[gradle] 03/04: Imported Upstream version 2.5

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 8 09:42:42 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.

commit 38e436cca91ddc48f8acf3104b072bf0b730ebf8
Author: Kai-Chung Yan <seamlikok at gmail.com>
Date:   Wed Jul 8 17:39:24 2015 +0800

    Imported Upstream version 2.5
---
 build.gradle                                       |   11 +-
 .../org/gradle/build/ReleasedVersions.groovy       |   29 +
 config/checkstyle/suppressions.xml                 |    4 +
 gradle/dependencies.gradle                         |   23 +-
 gradle/groovyProject.gradle                        |    2 +-
 gradle/idea.gradle                                 |   97 +-
 gradle/integTest.gradle                            |  165 ++-
 gradle/java9.gradle                                |   68 ++
 gradle/strictCompile.gradle                        |    8 +-
 gradle/testFixtures.gradle                         |   10 +-
 gradle/wrapper.gradle                              |    2 +-
 gradle/wrapper/gradle-wrapper.properties           |    4 +-
 gradlew                                            |    2 +-
 gradlew.bat                                        |    2 +-
 .../org/gradle/api/plugins/antlr/AntlrPlugin.java  |   11 +-
 .../org/gradle/api/plugins/antlr/AntlrTask.java    |    5 +
 .../ExpressionReplacingVisitorSupport.java         |  431 ++++++++
 .../internal/StatementReplacingVisitorSupport.java |  136 +++
 .../src/main/java/org/gradle/internal/Actions.java |   15 +
 .../main/java/org/gradle/internal/BiActions.java   |   20 +
 .../main/java/org/gradle/internal/FileUtils.java   |   49 +-
 .../src/main/java/org/gradle/internal/Specs.java   |   29 +
 .../org/gradle/internal/UncheckedException.java    |    2 +-
 .../internal/classloader/ClassLoaderFactory.java   |    2 +
 .../classloader/DefaultClassLoaderFactory.java     |   15 +-
 .../java/org/gradle/internal/jvm/JdkTools.java     |   25 +-
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |   24 +-
 .../internal/reflect/JavaReflectionUtil.java       |    8 +
 .../main/java/org/gradle/util/CollectionUtils.java |   28 +-
 .../org/gradle/internal/BiActionsTest.groovy       |   52 +
 .../org/gradle/internal/FileUtilsTest.groovy       |   28 +-
 .../groovy/org/gradle/internal/SpecsTest.groovy    |   38 +
 .../org/gradle/internal/jvm/JdkToolsTest.groovy    |    1 -
 .../groovy/org/gradle/internal/jvm/JvmTest.groovy  |   49 +-
 .../internal/reflect/JavaReflectionUtilTest.groovy |   21 +-
 .../org/gradle/util/CollectionUtilsTest.groovy     |    9 +-
 .../plugins/MavenConversionIntegrationTest.groovy  |    5 +-
 .../plugins/internal/maven/Maven2Gradle.groovy     |    2 +-
 .../internal/maven/MavenProjectXmlWriter.java      |    4 +-
 .../internal/maven/MavenProjectsCreator.java       |   27 +-
 ...LanguageLibraryProjectInitDescriptorSpec.groovy |    7 +-
 .../api/plugins/quality/CheckstylePlugin.groovy    |    6 +-
 .../api/plugins/quality/CodeNarcPlugin.groovy      |    6 +-
 .../api/plugins/quality/FindBugsPlugin.groovy      |    8 +-
 .../api/plugins/quality/JDependPlugin.groovy       |   10 +-
 .../gradle/api/plugins/quality/PmdPlugin.groovy    |   10 +-
 .../gradle/NativeServicesIntegrationTest.groovy    |   34 +
 .../ConfigurationOnDemandIntegrationTest.groovy    |   16 +-
 .../ExternalScriptExecutionIntegrationTest.groovy  |    2 +
 .../api/InitScriptExecutionIntegrationTest.groovy  |    2 +
 .../PluginApplicationErrorIntegrationTest.groovy   |    2 +
 .../SettingsScriptExecutionIntegrationTest.groovy  |    2 +
 .../api/dsl/PluginDetectionIntegrationTest.groovy  |    2 +
 .../ClassLoadersCachingIntegrationTest.groovy      |   11 +-
 .../gradle/api/tasks/ArchiveIntegrationTest.groovy |   51 +
 .../api/tasks/TaskRemovalIntegrationTest.groovy    |    4 -
 .../RuleBasedTaskExecutionIntegrationTest.groovy   |   10 +-
 .../ScriptPluginClassLoadingIntegrationTest.groovy |    2 +
 .../groovy/org/gradle/BuildExceptionReporter.java  |    2 +-
 .../src/main/groovy/org/gradle/StartParameter.java |   13 +-
 ...ExtensiblePolymorphicDomainObjectContainer.java |    3 +-
 .../src/main/groovy/org/gradle/api/Project.java    |   26 +
 .../api/artifacts/ComponentSelectionRules.java     |   12 +-
 .../org/gradle/api/artifacts/Configuration.java    |   38 +-
 .../api/artifacts/DependencySubstitution.java      |   22 +-
 .../api/artifacts/DependencySubstitutions.java     |  169 +---
 .../api/artifacts/FileCollectionDependency.java    |    5 +
 .../artifacts/ModuleDependencySubstitution.java    |   42 -
 .../artifacts/ProjectDependencySubstitution.java   |   31 -
 .../gradle/api/artifacts/ResolutionStrategy.java   |   48 +-
 .../org/gradle/api/artifacts/ResolveContext.java   |   28 +
 .../org/gradle/api/artifacts/ResolveException.java |   10 +-
 .../component/LibraryComponentIdentifier.java      |   28 +
 .../component/LibraryComponentSelector.java        |   29 +
 .../internal/InternalTaskExecutionListener.java    |   28 +
 .../api/execution/internal/TaskInputsListener.java |   41 +
 .../execution/internal/TaskOperationInternal.java  |   45 +
 .../groovy/org/gradle/api/file/FileCollection.java |    2 +
 .../main/groovy/org/gradle/api/file/FileTree.java  |    2 +
 .../org/gradle/api/internal/AbstractTask.java      |    6 +-
 .../DefaultPolymorphicDomainObjectContainer.java   |   69 +-
 .../DefaultPolymorphicNamedEntityInstantiator.java |   91 ++
 ...lePolymorphicDomainObjectContainerInternal.java |   23 +
 .../NoFactoryRegisteredForTypeException.java       |   20 +
 .../PolymorphicNamedEntityInstantiator.java        |   26 +
 .../org/gradle/api/internal/TaskInternal.java      |    2 -
 .../artifacts/DependencySubstitutionInternal.java  |    3 +-
 .../ModuleDependencySubstitutionInternal.java      |   25 -
 .../ProjectDependencySubstitutionInternal.java     |   23 -
 .../dependencies/DefaultProjectDependency.java     |    2 +-
 .../DefaultSelfResolvingDependency.java            |   15 +-
 .../api/internal/file/AbstractFileCollection.java  |   22 +-
 .../api/internal/file/AbstractFileResolver.java    |   13 +-
 .../gradle/api/internal/file/AbstractFileTree.java |   13 +-
 .../AntFileCollectionMatchingTaskBuilder.groovy    |   42 -
 .../file/AntFileCollectionMatchingTaskBuilder.java |   74 ++
 .../api/internal/file/CompositeFileCollection.java |   10 +-
 .../api/internal/file/CompositeFileTree.java       |   11 +-
 .../internal/file/DefaultCompositeFileTree.java    |    9 +-
 .../api/internal/file/DefaultFileOperations.java   |   19 +-
 .../internal/file/DefaultSourceDirectorySet.java   |    6 +-
 .../api/internal/file/FileCollectionInternal.java  |   37 +
 .../gradle/api/internal/file/FileOperations.java   |    4 +-
 .../org/gradle/api/internal/file/FileResolver.java |    9 +-
 .../gradle/api/internal/file/FileSystemSubset.java |  152 +++
 .../gradle/api/internal/file/FileTreeInternal.java |   22 +
 .../api/internal/file/ImmutableDirectoryTree.java  |   85 ++
 .../api/internal/file/ImmutablePatternSet.java     |  103 ++
 .../internal/file/MaybeCompressedFileResource.java |   21 +-
 .../gradle/api/internal/file/UnionFileTree.java    |   19 +-
 .../api/internal/file/archive/TarFileTree.java     |   19 +-
 .../api/internal/file/archive/ZipFileTree.java     |   14 +-
 .../file/archive/compression/Bzip2Archiver.java    |    2 +-
 .../compression/CompressedReadableResource.java    |   22 +
 .../file/archive/compression/GzipArchiver.java     |    2 +-
 ...pendenciesOnlyFileCollectionResolveContext.java |    6 +-
 .../DefaultFileCollectionResolveContext.java       |   27 +-
 .../file/collections/DelegatingFileCollection.java |   20 +-
 .../file/collections/DelegatingFileTree.java       |    5 +-
 .../file/collections/DirectoryFileTree.java        |   25 +-
 .../internal/file/collections/DirectoryTrees.java  |   45 +
 .../internal/file/collections/EmptyFileTree.java   |    6 +
 .../collections/FileBackedDirectoryFileTree.java   |   34 +
 .../internal/file/collections/FileTreeAdapter.java |   12 +
 .../LazilyInitializedFileCollection.java           |    8 +-
 .../collections/LazilyInitializedFileTree.java     |    8 +-
 .../api/internal/file/collections/MapFileTree.java |   53 +-
 .../internal/file/collections/MinimalFileTree.java |    3 +
 .../ResolvableFileCollectionResolveContext.java    |    8 +-
 .../collections/SingleIncludePatternFileTree.java  |    6 +
 .../file/collections/SingletonFileTree.java        |    7 +
 .../initialization/DefaultScriptHandler.java       |   46 +-
 .../DefaultScriptHandlerFactory.java               |    7 +-
 .../ScriptHandlerClassLoaderFactory.java           |   43 -
 .../initialization/ScriptHandlerFactory.java       |    5 +-
 .../initialization/ScriptHandlerInternal.java      |   26 +
 .../api/internal/project/AbstractProject.java      |   67 +-
 .../internal/resources/DefaultResourceHandler.java |   12 +-
 .../FileCollectionBackedArchiveTextResource.java   |   10 +-
 ...yRuleAwarePolymorphicDomainObjectContainer.java |   77 ++
 ...tRuleAwareNamedDomainObjectFactoryRegistry.java |   64 ++
 ...uleAwarePolymorphicNamedEntityInstantiator.java |   58 ++
 .../api/internal/rules/ModelMapCreators.java       |   74 ++
 .../rules/NamedDomainObjectFactoryRegistry.java    |   24 +
 .../RuleAwareNamedDomainObjectFactoryRegistry.java |   25 +
 .../RuleAwarePolymorphicDomainObjectContainer.java |   36 +
 ...uleAwarePolymorphicNamedEntityInstantiator.java |   23 +
 .../api/internal/tasks/DefaultTaskContainer.java   |   69 +-
 .../tasks/DefaultTaskContainerFactory.java         |   57 +-
 .../api/internal/tasks/TaskContainerInternal.java  |    2 +-
 .../api/internal/tasks/TaskStateInternal.java      |    8 +-
 .../SkipEmptySourceFilesTaskExecuter.java          |   14 +-
 .../org/gradle/api/tasks/AntBuilderAware.groovy    |   25 -
 .../org/gradle/api/tasks/AntBuilderAware.java      |   24 +
 .../groovy/org/gradle/api/tasks/GradleBuild.java   |    2 +-
 .../groovy/org/gradle/api/tasks/SourceTask.java    |    3 +-
 .../groovy/org/gradle/api/tasks/TaskState.java     |   11 +
 .../tasks/incremental/IncrementalTaskInputs.java   |    5 +-
 .../org/gradle/api/tasks/util/PatternSet.java      |   51 +-
 .../configuration/DefaultScriptPluginFactory.java  |    7 +-
 .../execution/CancellableOperationManager.java     |   29 +
 .../DefaultCancellableOperationManager.java        |   70 ++
 .../PassThruCancellableOperationManager.java       |   33 +
 .../taskgraph/AbstractTaskPlanExecutor.java        |   33 +-
 .../taskgraph/DefaultTaskGraphExecuter.java        |   70 +-
 .../taskgraph/DefaultTaskPlanExecutor.java         |    8 +-
 .../taskgraph/ParallelTaskPlanExecutor.java        |   14 +-
 .../execution/taskgraph/TaskPlanExecutor.java      |   11 +-
 .../org/gradle/groovy/scripts/DefaultScript.java   |   48 +-
 .../initialization/BuildCancellationToken.java     |    4 +
 .../gradle/initialization/BuildRequestContext.java |   12 +
 .../DefaultBuildCancellationToken.java             |    3 +-
 .../initialization/DefaultBuildRequestContext.java |   27 +-
 .../DefaultCommandLineConverter.java               |    8 +
 .../initialization/DefaultGradleLauncher.java      |  133 ++-
 .../DefaultGradleLauncherFactory.java              |   51 +-
 .../FixedBuildCancellationToken.java               |   33 -
 .../org/gradle/initialization/GradleLauncher.java  |    9 +-
 .../gradle/initialization/ReportedException.java   |   25 +
 .../buildsrc/BuildSrcUpdateFactory.java            |    4 +-
 .../filewatch/DefaultFileSystemChangeWaiter.java   |  138 +++
 .../filewatch/DefaultFileWatcherFactory.java       |   72 ++
 .../internal/filewatch/FileSystemChangeWaiter.java |   24 +
 .../org/gradle/internal/filewatch/FileWatcher.java |   37 +
 .../internal/filewatch/FileWatcherEvent.java       |   70 ++
 .../internal/filewatch/FileWatcherFactory.java     |   40 +
 .../internal/filewatch/FileWatcherListener.java    |   21 +
 .../filewatch/jdk7/Jdk7FileWatcherFactory.java     |   51 +
 .../jdk7/WatchServiceFileWatcherBacking.java       |  153 +++
 .../filewatch/jdk7/WatchServicePoller.java         |   84 ++
 .../filewatch/jdk7/WatchServiceRegistrar.java      |  158 +++
 .../internal/progress/BuildOperationExecutor.java  |   47 +
 .../internal/progress/BuildOperationInternal.java  |   43 +
 .../internal/progress/BuildOperationType.java      |   45 +
 .../progress/DefaultBuildOperationExecutor.java    |   79 ++
 .../internal/progress/InternalBuildListener.java   |   24 +
 .../internal/progress/OperationIdGenerator.java    |   57 ++
 .../internal/progress/OperationIdentifier.java     |   29 +-
 .../gradle/internal/progress/OperationResult.java  |   44 +
 .../internal/progress/OperationStartEvent.java     |   29 +
 .../internal/progress/OperationsHierarchy.java     |   15 +-
 .../progress/OperationsHierarchyKeeper.java        |    2 +-
 .../service/scopes/BuildScopeServices.java         |    8 +
 .../service/scopes/GlobalScopeServices.java        |   29 +-
 .../service/scopes/GradleScopeServices.java        |   14 +-
 .../service/scopes/TaskExecutionServices.java      |   61 +-
 .../org/gradle/internal/text/TreeFormatter.java    |    2 +-
 .../groovy/org/gradle/logging/ProgressLogger.java  |    4 +-
 .../internal/ConsoleBackedProgressRenderer.java    |    6 +-
 .../internal/DefaultProgressLoggerFactory.java     |    4 +-
 .../internal/LinePrefixingStyledTextOutput.java    |   11 +
 .../logging/internal/ProgressCompleteEvent.java    |    7 +-
 .../org/gradle/logging/internal/ProgressEvent.java |    7 +-
 .../internal/ProgressLogEventGenerator.java        |   28 +-
 .../logging/internal/ProgressStartEvent.java       |   13 +-
 .../collection/internal/BridgedCollections.java    |  218 ++--
 .../DomainObjectContainerModelProjection.java      |  137 ---
 ...cTypesDomainObjectContainerModelProjection.java |   45 -
 .../internal/PolymorphicModelMapProjection.java    |   50 +
 ...icTypeDomainObjectContainerModelProjection.java |   64 --
 .../use/internal/PluginRequestApplicator.java      |    4 +-
 .../gradle/process/internal/DefaultExecHandle.java |    5 +-
 .../gradle/process/internal/ExecHandleRunner.java  |    7 +-
 .../process/internal/streams/StreamsForwarder.java |    6 +-
 .../process/internal/streams/StreamsHandler.java   |    3 +-
 .../internal/TestBuildScopeServices.java           |    4 +-
 .../org/gradle/api/file/ProjectCopySpecTest.groovy |    3 +-
 ...ltPolymorphicNamedEntityInstantiatorTest.groovy |  108 ++
 .../org/gradle/api/internal/DefaultTaskTest.groovy |   21 +-
 .../DefaultProjectDependencyTest.groovy            |   25 +-
 .../DefaultSelfResolvingDependencyTest.java        |   16 +-
 .../internal/file/AbstractFileCollectionTest.java  |    2 +-
 .../internal/file/BaseDirFileResolverSpec.groovy   |   10 +-
 .../internal/file/CompositeFileCollectionTest.java |   18 +-
 .../api/internal/file/CompositeFileTreeTest.java   |   18 +-
 .../internal/file/DefaultFileOperationsTest.groovy |    4 +-
 .../file/DelegatingFileCollectionTest.groovy       |   28 +-
 .../api/internal/file/FileSystemSubsetTest.groovy  |  138 +++
 .../LazilyInitializedFileCollectionTest.groovy     |    4 +-
 .../file/MaybeCompressedFileResourceTest.groovy    |   36 +-
 .../api/internal/file/UnionFileCollectionTest.java |    5 +-
 .../api/internal/file/UnionFileTreeTest.java       |    3 +-
 .../internal/file/archive/TarCopyActionTest.java   |    2 +-
 .../api/internal/file/archive/TarFileTreeTest.java |   16 +-
 .../DefaultConfigurableFileCollectionTest.java     |    5 +-
 .../DefaultFileCollectionResolveContextTest.groovy |  871 ++++++++--------
 .../file/collections/FileTreeAdapterTest.groovy    |    4 +-
 .../internal/file/collections/MapFileTreeTest.java |   26 +-
 .../file/copy/DefaultCopySpecResolutionTest.groovy |    6 +-
 .../DefaultScriptHandlerFactoryTest.groovy         |   93 --
 .../initialization/DefaultScriptHandlerTest.groovy |   56 +-
 .../DefaultObjectConfigurationActionTest.groovy    |    4 +-
 .../internal/plugins/ExtensionContainerTest.groovy |    8 +-
 .../AnnotationProcessingTaskFactoryTest.java       |  118 +--
 ...warePolymorphicDomainObjectContainerTest.groovy |   75 ++
 ...wareNamedDomainObjectFactoryRegistryTest.groovy |   51 +
 ...rePolymorphicNamedEntityInstantiatorTest.groovy |   49 +
 ...warePolymorphicDomainObjectContainerTest.groovy |   66 ++
 .../internal/tasks/DefaultTaskInputsTest.groovy    |    6 +-
 .../SkipEmptySourceFilesTaskExecuterTest.groovy    |   11 +-
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |   17 +-
 .../gradle/api/tasks/util/PatternSetTest.groovy    |   89 +-
 .../internal/AbstractFileLockManagerTest.groovy    |    2 +
 .../cache/internal/DefaultCacheFactoryTest.groovy  |   25 +-
 .../DefaultPersistentDirectoryCacheSpec.groovy     |    7 +-
 ...tPersistentDirectoryStoreConcurrencyTest.groovy |    3 +
 .../btree/BTreePersistentIndexedCacheTest.java     |   73 +-
 .../DefaultInitScriptProcessorTest.groovy          |    4 +-
 .../DefaultScriptPluginFactoryTest.groovy          |    3 +-
 .../DefaultCancellableOperationManagerTest.groovy  |  116 +++
 .../gradle/execution/TaskNameResolverTest.groovy   |   24 +-
 .../taskgraph/DefaultTaskGraphExecuterSpec.groovy  |  215 +++-
 .../taskgraph/DefaultTaskGraphExecuterTest.java    |   77 +-
 .../taskgraph/DefaultTaskPlanExecutorTest.groovy   |   21 +-
 .../taskgraph/TaskPlanExecutorFactoryTest.groovy   |    3 +-
 .../CommandLineConverterTestSupport.java           |  103 ++
 .../DefaultBuildCancellationTokenSpec.groovy       |   14 +-
 .../DefaultCommandLineConverterTest.groovy         |  445 --------
 .../DefaultCommandLineConverterTest.java           |  362 +++++++
 .../DefaultGradleLauncherFactoryTest.groovy        |    2 +-
 .../initialization/DefaultGradleLauncherTest.java  |   80 +-
 .../ParallelOptionsCommandLineConverterTest.groovy |   55 +
 .../DefaultFileSystemChangeWaiterTest.groovy       |  173 ++++
 .../DefaultFileWatcherFactoryNonJava7Test.groovy   |   33 +
 .../filewatch/DefaultFileWatcherFactoryTest.groovy |  261 +++++
 .../jdk7/WatchServiceFileWatcherBackingTest.groovy |   64 ++
 .../DefaultBuildOperationExecutorTest.groovy       |  214 ++++
 .../progress/OperationsHierarchyKeeperTest.groovy  |    4 +-
 .../progress/OperationsHierarchyTest.groovy        |   16 +-
 .../service/scopes/GradleScopeServicesTest.groovy  |   12 +-
 .../scopes/TaskExecutionServicesTest.groovy        |    3 +-
 .../LinePrefixingStyledTextOutputTest.groovy       |  135 +++
 .../logging/internal/OutputSpecification.groovy    |   17 +-
 .../internal/ProgressLogEventGeneratorTest.groovy  |   34 +
 .../process/internal/DefaultExecHandleSpec.groovy  |    5 +-
 .../internal/DefaultWorkerProcessTest.groovy       |    2 +
 .../org/gradle/api/tasks/AbstractTaskTest.java     |    7 +
 .../dependency-management.gradle                   |   47 +-
 .../ArtifactDependenciesIntegrationTest.groovy     |    3 +
 .../ComponentReplacementIntegrationTest.groovy     |   26 +-
 .../ConfigurationDefaultsIntegrationTest.groovy    |  127 +++
 .../DependencyExcludeResolveIntegrationTest.groovy |    3 +
 ...ependencyResolutionEventsIntegrationTest.groovy |    2 +
 .../DependencyResolveRulesIntegrationTest.groovy   |    7 +-
 ...pendencySubstitutionRulesIntegrationTest.groovy |  722 +++++++------
 .../DetachedConfigurationsIntegrationTest.groovy   |    3 +
 .../ExtendingConfigurationsIntegrationTest.groovy  |    3 +
 .../MetadataArtifactResolveTestFixture.groovy      |   29 +-
 .../ProjectDependenciesIntegrationTest.groovy      |    3 +
 .../ProjectDependencyResolveIntegrationTest.groovy |  144 ++-
 .../PublishAndResolveIntegrationTest.groovy        |  183 ++++
 .../ResolutionResultApiIntegrationTest.groovy      |    3 +
 .../ResolvedConfigurationIntegrationTest.groovy    |    3 +
 .../UnsupportedConfigurationMutationTest.groovy    |  270 ++++-
 ...rDependencyExcludeResolveIntegrationTest.groovy |   42 +-
 .../ivy/IvyHttpsRepoResolveIntegrationTest.groovy  |    2 +
 ...yModuleArtifactResolutionIntegrationTest.groovy |    2 +-
 .../MavenHttpsRepoResolveIntegrationTest.groovy    |    2 +
 ...nModuleArtifactResolutionIntegrationTest.groovy |    2 +-
 .../artifacts/ArtifactDependencyResolver.java      |    9 +-
 .../internal/artifacts/ConfigurationResolver.java  |    3 +-
 .../DefaultDependencyManagementServices.java       |    5 +-
 .../artifacts/DefaultResolvedArtifact.java         |    8 +-
 .../DependencyManagementBuildScopeServices.java    |    6 +-
 .../DependencyManagementGlobalScopeServices.java   |    7 +-
 .../internal/artifacts/ResolveContextInternal.java |   31 +
 .../api/internal/artifacts/ResolverResults.java    |   70 +-
 .../configurations/ConfigurationInternal.java      |   17 +-
 .../configurations/DefaultConfiguration.java       |  391 +++++--
 .../DefaultConfigurationContainer.java             |    9 +-
 .../configurations/MutationValidator.java          |   14 +-
 .../configurations/ResolutionStrategyInternal.java |   22 +-
 .../dsl/ComponentModuleMetadataContainer.java      |    4 +-
 .../dsl/DefaultComponentMetadataHandler.java       |    4 +-
 .../ivyservice/AbstractDependencySubstitution.java |   73 --
 .../CacheLockingArtifactDependencyResolver.java    |   21 +-
 .../ivyservice/DefaultConfigurationResolver.java   |    9 +-
 .../DefaultDependencyResolveDetails.java           |   95 --
 .../ivyservice/DefaultIvyDependencyPublisher.java  |    2 +-
 .../ivyservice/DefaultLenientConfiguration.java    |   45 +-
 .../DefaultModuleDependencySubstitution.java       |   50 -
 .../DefaultProjectDependencySubstitution.java      |   28 -
 .../ivyservice/DependencySubstitutionResolver.java |   61 --
 .../ErrorHandlingArtifactDependencyResolver.java   |   93 +-
 .../ivyservice/IvyBackedArtifactPublisher.java     |   17 +-
 .../ivyservice/IvyModuleDescriptorWriter.java      |    3 +
 .../api/internal/artifacts/ivyservice/IvyUtil.java |   28 +-
 .../ivyservice/IvyXmlModuleDescriptorWriter.java   |   59 +-
 .../ivyservice/LocalComponentFactory.java          |   10 +-
 .../SelfResolvingDependencyResolver.java           |   19 +-
 ...cuitEmptyConfigsArtifactDependencyResolver.java |   31 +-
 .../clientmodule/ClientModuleResolver.java         |   24 +-
 .../DefaultDependencyResolveDetails.java           |   95 ++
 .../DefaultDependencySubstitution.java             |   81 ++
 .../DefaultDependencySubstitutions.java            |  196 ++++
 .../DependencySubstitutionResolver.java            |   52 +
 .../DependencySubstitutionsInternal.java           |   35 +
 .../ModuleSelectorStringNotationConverter.java     |   61 ++
 .../UnversionedModuleComponentSelector.java        |   62 ++
 .../BaseModuleComponentRepositoryAccess.java       |    4 +-
 ...cheLockReleasingModuleComponentsRepository.java |    7 +-
 .../CachingModuleComponentRepository.java          |   19 +-
 .../ivyresolve/ComponentMetaDataResolveState.java  |   10 +-
 .../DefaultVersionedComponentChooser.java          |    6 +-
 .../ivyresolve/DynamicVersionResolver.java         |   10 +-
 .../ErrorHandlingModuleComponentRepository.java    |    4 +-
 ...amicResolveModuleComponentRepositoryAccess.java |    7 +-
 .../ivyresolve/LocalModuleComponentRepository.java |    8 +-
 .../ivyresolve/ModuleComponentRepository.java      |    3 -
 .../ModuleComponentRepositoryAccess.java           |    7 +-
 .../ivyresolve/NoRepositoriesResolver.java         |    9 +-
 .../ivyservice/ivyresolve/RepositoryChain.java     |    5 +-
 .../ivyresolve/RepositoryChainAdapter.java         |   71 --
 .../RepositoryChainComponentMetaDataResolver.java  |  145 +++
 .../RepositoryChainDependencyResolver.java         |  143 ---
 ...sitoryChainDependencyToComponentIdResolver.java |   52 +
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |   33 +-
 .../StartParameterResolutionOverride.java          |    2 +-
 .../ivyservice/ivyresolve/UserResolverChain.java   |   30 +-
 .../ivyresolve/VersionedComponentChooser.java      |    4 +-
 .../InMemoryCachedModuleComponentRepository.java   |    5 +-
 .../ivyresolve/parser/DescriptorParseContext.java  |    4 +-
 .../parser/DisconnectedDescriptorParseContext.java |    4 +-
 .../parser/GradlePomModuleDescriptorParser.java    |   12 +-
 .../parser/IvyXmlModuleDescriptorParser.java       |    8 +-
 .../CachedModuleDescriptorParseContext.java        |    6 +-
 .../moduleconverter/ComponentConverterSource.java  |   39 +
 .../CompositeResolveLocalComponentFactory.java     |   50 +
 .../DefaultConfigurationsToArtifactsConverter.java |   27 +-
 ...tConfigurationsToModuleDescriptorConverter.java |    8 +-
 .../ResolveLocalComponentFactory.java              |   39 +-
 .../AbstractIvyDependencyDescriptorFactory.java    |   60 +-
 ...ultDependenciesToModuleDescriptorConverter.java |    2 +-
 .../DefaultDependencyDescriptorFactory.java        |    5 +-
 .../dependencies/DependencyDescriptorFactory.java  |    3 +-
 ...ternalModuleIvyDependencyDescriptorFactory.java |   43 +-
 .../IvyDependencyDescriptorFactory.java            |    3 +-
 .../ProjectIvyDependencyDescriptorFactory.java     |   36 +-
 .../DefaultProjectComponentRegistry.java           |    8 +-
 .../projectmodule/ProjectArtifactResolver.java     |   20 +-
 .../projectmodule/ProjectDependencyResolver.java   |   52 +-
 .../resolutionstrategy/DefaultCachePolicy.java     |    2 +-
 .../DefaultComponentSelectionRules.java            |    6 +-
 .../DefaultDependencySubstitutions.java            |  267 -----
 .../DefaultResolutionStrategy.java                 |   30 +-
 .../DependencySubstitutionsInternal.java           |   34 -
 .../ModuleForcingResolveRule.java                  |   12 +-
 .../resolveengine/DefaultDependencyResolver.java   |   68 +-
 .../DefaultDependencyToConfigurationResolver.java  |   33 +-
 .../DefaultModuleResolutionFilter.java             |  190 +++-
 .../resolveengine/ModuleResolutionFilter.java      |   13 +-
 .../resolveengine/graph/AbstractArtifactSet.java   |  102 ++
 .../resolveengine/graph/ArtifactSet.java           |   28 +
 .../graph/ConfigurationArtifactSet.java            |   53 +
 .../resolveengine/graph/DependencyArtifactSet.java |   48 +
 .../graph/DependencyGraphBuilder.java              |   91 +-
 .../ResolutionResultDependencyGraphVisitor.java    |    2 +-
 ...esolvedConfigurationDependencyGraphVisitor.java |   64 +-
 ...lvedProjectConfigurationResultGraphVisitor.java |    4 +-
 .../oldresult/DefaultResolvedArtifactResults.java  |   71 ++
 .../oldresult/DefaultResolvedArtifactsBuilder.java |   33 +
 .../DefaultResolvedConfigurationBuilder.java       |   83 +-
 .../oldresult/DefaultResolvedGraphResults.java     |   51 +
 .../oldresult/ResolvedArtifactResults.java         |   29 +
 .../oldresult/ResolvedArtifactsBuilder.java        |   26 +
 .../oldresult/ResolvedConfigurationBuilder.java    |   14 +-
 .../oldresult/ResolvedConfigurationResults.java    |   32 -
 .../oldresult/ResolvedContentsMapping.java         |    4 +-
 .../oldresult/ResolvedGraphResults.java            |   31 +
 .../TransientConfigurationResultsBuilder.java      |    4 +-
 .../TransientConfigurationResultsLoader.java       |   53 +
 .../DefaultResolvedProjectConfiguration.java       |   44 +
 .../DefaultResolvedProjectConfigurationResult.java |   74 --
 ...tResolvedProjectConfigurationResultBuilder.java |   28 +-
 ...DefaultResolvedProjectConfigurationResults.java |    8 +-
 .../ResolvedProjectConfiguration.java              |   25 +
 .../ResolvedProjectConfigurationResult.java        |   27 -
 .../ResolvedProjectConfigurationResults.java       |    4 +-
 .../result/ComponentIdentifierSerializer.java      |   15 +-
 .../result/ComponentSelectorSerializer.java        |   29 +-
 .../resolveengine/store/DefaultBinaryStore.java    |    4 +-
 .../DefaultLocalMavenRepositoryLocator.java        |    4 +-
 .../mvnsettings/DefaultMavenSettingsProvider.java  |    4 +-
 .../mvnsettings/MavenSettingsProvider.java         |    4 +-
 .../query/DefaultArtifactResolutionQuery.java      |   28 +-
 .../resolver/ExternalResourceResolver.java         |   38 +-
 ...rnalResourceResolverDescriptorParseContext.java |   18 +-
 .../repositories/resolver/IvyResolver.java         |   15 +-
 .../repositories/resolver/MavenResolver.java       |   20 +-
 .../DependencyClassPathNotationConverter.java      |    4 +-
 .../ModuleIdentiferNotationConverter.java          |   64 --
 .../ModuleIdentifierNotationConverter.java         |   67 ++
 .../AbstractModuleComponentResolveMetaData.java    |   32 +-
 .../model/BuildableIvyModulePublishMetaData.java   |   12 +
 .../model/BuildableIvyModuleResolveMetaData.java   |    2 +-
 .../model/DefaultIvyModulePublishMetaData.java     |   98 +-
 .../model/DefaultIvyModuleResolveMetaData.java     |    9 +-
 .../model/DefaultMavenModuleResolveMetaData.java   |    7 +-
 .../DefaultModuleComponentArtifactIdentifier.java  |    5 -
 .../DefaultModuleComponentArtifactMetaData.java    |   12 -
 .../external/model/IvyModulePublishMetaData.java   |    6 +
 .../model/ModuleComponentArtifactMetaData.java     |    6 -
 .../model/ModuleComponentResolveMetaData.java      |   15 +-
 .../model/DefaultLibraryComponentIdentifier.java   |   74 ++
 .../model/DefaultLibraryComponentSelector.java     |   83 ++
 .../model/DefaultLocalArtifactIdentifier.java      |   72 --
 .../local/model/DefaultLocalComponentMetaData.java |  288 ++++--
 .../model/DslOriginDependencyMetaDataWrapper.java  |   30 +-
 .../local/model/LocalArtifactMetaData.java         |   25 -
 .../model/LocalComponentArtifactIdentifier.java    |   25 +
 .../local/model/LocalComponentMetaData.java        |    4 -
 .../local/model/LocalConfigurationMetaData.java    |   28 +
 .../local/model/MissingLocalArtifactMetaData.java  |   84 ++
 .../local/model/MutableLocalComponentMetaData.java |   11 +-
 .../PublishArtifactLocalArtifactMetaData.java      |  106 ++
 .../AbstractModuleDescriptorBackedMetaData.java    |   19 +-
 .../component/model/ComponentOverrideMetadata.java |   40 +
 .../component/model/ComponentResolveMetaData.java  |   28 +-
 .../component/model/ConfigurationMetaData.java     |   11 +
 .../model/DefaultComponentOverrideMetadata.java    |   75 ++
 .../component/model/DefaultDependencyMetaData.java |   38 +-
 .../component/model/DefaultIvyArtifactName.java    |   27 +-
 .../component/model/DependencyMetaData.java        |   31 +-
 .../model/LocalComponentDependencyMetaData.java    |  156 +++
 .../model/ModuleComponentArtifactsMetaData.java    |   20 +
 .../resolver/ComponentMetaDataResolver.java        |    4 +-
 .../resolver/DependencyToComponentResolver.java    |   29 -
 .../resolver/ModuleToComponentResolver.java        |   30 -
 .../ResolveContextToComponentResolver.java         |   27 +
 .../result/BuildableComponentResolveResult.java    |    4 +-
 .../DefaultBuildableComponentResolveResult.java    |    7 +-
 .../artifacts/DefaultResolvedArtifactTest.groovy   |    8 +-
 .../artifacts/DefaultResolvedDependencyTest.java   |    2 +-
 .../internal/artifacts/ResolverResultsSpec.groovy  |   28 +-
 .../DefaultConfigurationContainerSpec.groovy       |    8 +-
 .../DefaultConfigurationContainerTest.groovy       |    6 +-
 .../configurations/DefaultConfigurationSpec.groovy | 1068 +++++++++++++++++++-
 .../configurations/DefaultConfigurationTest.java   |  906 -----------------
 .../DefaultDependencyResolveDetailsSpec.groovy     |  154 ---
 .../DefaultModuleDependencySubstitutionTest.groovy |  192 ----
 ...DefaultProjectDependencySubstitutionTest.groovy |  102 --
 .../DependencySubstitutionResolverSpec.groovy      |   84 --
 ...orHandlingArtifactDependencyResolverTest.groovy |   12 +-
 .../SelfResolvingDependencyResolverTest.groovy     |   12 +-
 ...ptyConfigsArtifactDependencyResolverSpec.groovy |    1 +
 .../clientmodule/ClientModuleResolverTest.groovy   |   25 +-
 .../DefaultDependencyResolveDetailsSpec.groovy     |  153 +++
 .../DefaultDependencySubstitutionSpec.groovy       |   85 ++
 .../DefaultDependencySubstitutionsSpec.groovy      |  245 +++++
 .../DependencySubstitutionResolverSpec.groovy      |   83 ++
 ...oduleSelectorStringNotationConverterTest.groovy |   59 ++
 .../CachingModuleComponentRepositoryTest.groovy    |    6 +-
 .../DefaultVersionedComponentChooserTest.groovy    |   16 +-
 ...solveModuleComponentRepositoryAccessTest.groovy |   19 +-
 .../ivyresolve/RepositoryChainAdapterTest.groovy   |   72 --
 ...sitoryChainComponentMetaDataResolverTest.groovy |  580 +++++++++++
 .../RepositoryChainDependencyResolverTest.groovy   |  582 -----------
 .../ivyresolve/ResolveIvyFactoryTest.groovy        |   11 +-
 ...emoryCachedModuleComponentRepositoryTest.groovy |   30 +-
 ...adlePomModuleDescriptorParserProfileTest.groovy |   42 +-
 .../GradlePomModuleDescriptorParserTest.groovy     |   52 +-
 .../modulecache/ModuleDescriptorStoreTest.groovy   |    3 +-
 ...ltConfigurationsToArtifactsConverterTest.groovy |   66 +-
 ...figurationsToModuleDescriptorConverterTest.java |   17 +-
 .../ResolveLocalComponentFactoryTest.groovy        |   10 +-
 ...actDependencyDescriptorFactoryInternalTest.java |   48 +-
 ...endenciesToModuleDescriptorConverterTest.groovy |    2 +-
 .../DefaultDependencyDescriptorFactoryTest.groovy  |    6 +-
 ...ernalModuleDependencyDescriptorFactoryTest.java |   21 +-
 .../ProjectDependencyDescriptorFactoryTest.groovy  |   16 +-
 .../ProjectDependencyResolverTest.groovy           |   59 +-
 .../DefaultCachePolicySpec.groovy                  |    4 +-
 .../DefaultComponentSelectionRulesTest.groovy      |    2 +-
 .../DefaultDependencySubstitutionsSpec.groovy      |  328 ------
 .../DefaultResolutionStrategySpec.groovy           |   19 +-
 .../ModuleForcingResolveRuleSpec.groovy            |   13 +-
 .../DefaultModuleResolutionFilterTest.groovy       |   52 +-
 .../DependencyGraphBuilderTest.groovy              |   54 +-
 .../ComponentIdentifierSerializerTest.groovy       |   14 +
 .../result/ComponentSelectorSerializerTest.groovy  |   18 +-
 .../store/DefaultBinaryStoreTest.groovy            |    8 +
 .../DefaultArtifactResolutionQueryTest.groovy      |   14 +-
 ...DependencyClassPathNotationConverterTest.groovy |    4 +-
 .../ModuleIdentiferNotationConverterTest.groovy    |   52 -
 .../ModuleIdentifierNotationConverterTest.groovy   |   52 +
 ...stractModuleComponentResolveMetaDataTest.groovy |   33 -
 .../DefaultIvyModulePublishMetaDataTest.groovy     |   60 +-
 ...faultModuleComponentArtifactMetaDataTest.groovy |   21 +-
 .../DefaultLibraryComponentIdentifierTest.groovy   |   69 ++
 .../DefaultLibraryComponentSelectorTest.groovy     |  104 ++
 .../DefaultLocalArtifactIdentifierTest.groovy      |   67 --
 .../model/DefaultLocalComponentMetaDataTest.groovy |  185 ++--
 .../model/MissingLocalArtifactMetaDataTest.groovy  |   75 ++
 .../model/DefaultDependencyMetaDataTest.groovy     |    4 +-
 .../model/DefaultIvyArtifactNameTest.groovy        |   27 +
 ...faultBuildableComponentResolveResultTest.groovy |    7 +-
 .../ComponentReportIntegrationTest.groovy          |   15 +-
 .../model/ModelReportIntegrationTest.groovy        |  151 ++-
 .../model/ModelReportTaskIntegrationTest.groovy    |   32 +
 ...pendencyInsightReportTaskIntegrationTest.groovy |   15 +-
 .../DependencyReportTaskIntegrationTest.groovy     |   53 +-
 .../api/reporting/components/ComponentReport.java  |    6 +-
 .../components/internal/ComponentRenderer.java     |   10 +-
 .../internal/ComponentReportRenderer.java          |   14 +-
 .../components/internal/SourceSetRenderer.java     |   46 +-
 .../internal/TypeAwareBinaryRenderer.java          |    6 +
 .../gradle/api/reporting/model/ModelReport.java    |   24 +-
 .../model/internal/ModelNodeRenderer.java          |   61 ++
 .../model/internal/ModelReportRenderer.java        |   56 -
 .../model/internal/TextModelReportRenderer.java    |   32 +
 .../internal/text/DefaultTextReportBuilder.java    |    9 +-
 .../gradle/configuration/TaskDetailPrinter.java    |    2 +-
 .../internal/ComponentRendererTest.groovy          |   18 +-
 .../internal/ComponentReportRendererTest.groovy    |   17 +-
 .../internal/SourceSetRendererTest.groovy          |   87 +-
 .../AbstractComponentReportIntegrationTest.groovy  |    5 +-
 .../ComponentReportOutputFormatter.groovy          |   18 +-
 .../NativeComponentReportIntegrationTest.groovy    |   23 +
 .../NativeComponentReportOutputFormatter.groovy    |   45 +
 .../api/reporting/model/ConsoleReportOutput.groovy |   76 ++
 .../gradle/AllDistributionIntegrationSpec.groovy   |    4 +-
 .../gradle/BinDistributionIntegrationSpec.groovy   |    2 +-
 .../org/gradle/DistributionIntegrationSpec.groovy  |    2 +-
 subprojects/docs/docs.gradle                       |    3 +-
 .../dsl/org.gradle.api.artifacts.Configuration.xml |    3 +
 ...radle.api.artifacts.DependencySubstitutions.xml |   34 +
 ...org.gradle.api.artifacts.ResolutionStrategy.xml |    8 +-
 ...adle.api.tasks.compile.GroovyCompileOptions.xml |    6 +-
 .../dsl/org.gradle.language.DependentSourceSet.xml |   44 -
 ....language.nativeplatform.DependentSourceSet.xml |   47 +
 ...ge.nativeplatform.HeaderExportingSourceSet.xml} |    0
 ...tform.tasks.AbstractNativeSourceCompileTask.xml |   38 +
 subprojects/docs/src/docs/dsl/plugins.xml          |    1 -
 subprojects/docs/src/docs/release/notes-next.md    |  113 ---
 .../docs/src/docs/release/notes-template.md        |    2 +-
 subprojects/docs/src/docs/release/notes.md         |  570 +++++------
 .../docs/src/docs/userguide/antlrPlugin.xml        |    8 +-
 .../docs/src/docs/userguide/commandLine.xml        |   33 +-
 .../docs/src/docs/userguide/continuousBuild.xml    |  162 +++
 .../docs/src/docs/userguide/customTasks.xml        |    5 +
 subprojects/docs/src/docs/userguide/depMngmt.xml   |  474 +++++----
 .../docs/src/docs/userguide/distributionPlugin.xml |    4 +-
 .../docs/src/docs/userguide/nativeBinaries.xml     |   44 +-
 subprojects/docs/src/docs/userguide/newModel.xml   |   14 +-
 .../docs/src/docs/userguide/signingPlugin.xml      |   30 +-
 .../docs/src/docs/userguide/sonarRunnerPlugin.xml  |  121 ++-
 subprojects/docs/src/docs/userguide/userguide.xml  |    1 +
 .../docs/src/docs/userguide/workingWithFiles.xml   |    4 +-
 subprojects/docs/src/samples/antlr/build.gradle    |    6 +-
 .../samples/customModel/componentType/build.gradle |    6 +-
 .../documentation/DocumentationPlugin.groovy       |    8 +-
 .../groovy/sample/markdown/MarkdownPlugin.groovy   |    6 +-
 .../samples/dependency-substitution/build.gradle   |   49 +
 .../dependency-substitution/project1/build.gradle  |    3 +
 .../dependency-substitution/project2/build.gradle  |    3 +
 .../dependency-substitution/project3/build.gradle  |    2 +
 .../repo/org.example/project1/1.0/ivy-1.0.xml      |   20 +
 .../repo/org.example/project2/1.0/ivy-1.0.xml      |   20 +
 .../repo/org.example/project3/1.0/ivy-1.0.xml      |   17 +
 .../dependency-substitution/settings.gradle        |   18 +
 .../modelRules/basicRuleSourcePlugin/build.gradle  |    2 +-
 .../src/samples/modelRules/modelDsl/build.gradle   |    2 +-
 .../native-binaries/google-test/build.gradle       |    5 +
 .../pre-compiled-headers/build.gradle              |   59 ++
 .../pre-compiled-headers/src/hello/cpp/hello.cpp   |    5 +
 .../pre-compiled-headers/src/hello/headers/hello.h |   13 +
 .../pre-compiled-headers/src/hello/headers/pch.h   |    5 +
 .../pre-compiled-headers/src/main/cpp/main.cpp     |    7 +
 .../artifacts/defineConfiguration/build.gradle     |   10 +
 .../artifacts/dependency-substitution/build.gradle |   16 +
 .../artifacts/resolutionStrategy/build.gradle      |   16 +-
 .../userguide/tasks/incrementalTask/build.gradle   |    5 +
 .../basicRuleSourcePlugin-model-task.out           |   16 +-
 .../userguideOutput/nativeComponentReport.out      |    4 +-
 .../main/groovy/org/gradle/plugins/ear/Ear.groovy  |    2 +-
 ...VisualStudioSingleProjectIntegrationTest.groovy |   43 +
 .../gradle/ide/cdt/model/CprojectSettings.groovy   |    6 +-
 subprojects/ide/ide.gradle                         |    1 -
 .../eclipse/AbstractEclipseIntegrationTest.groovy  |  177 ++--
 .../eclipse/EclipseClasspathIntegrationTest.groovy |  198 +++-
 ...pseDependencySubstitutionIntegrationTest.groovy |   14 +-
 .../ide/eclipse/EclipseIntegrationTest.groovy      |  116 ++-
 ...lipseWtpWebAndJavaProjectIntegrationTest.groovy |    4 +-
 ...deaDependencySubstitutionIntegrationTest.groovy |   15 +-
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |   23 +-
 .../ide/idea/IdeaMultiModuleIntegrationTest.groovy |   70 +-
 .../src/main/java/org/gradle/api/PersonList.java   |    4 +-
 .../expectedFiles/apiClasspath.xml                 |   13 +-
 .../expectedFiles/commonClasspath.xml              |   21 +-
 .../expectedFiles/groovyprojectClasspath.xml       |   11 +-
 .../expectedFiles/javabaseprojectClasspath.xml     |    2 +-
 .../expectedFiles/webAppJava6Classpath.xml         |   17 +-
 .../expectedFiles/webAppJava6WtpComponent.xml      |   16 +
 .../expectedFiles/webAppWithVarsClasspath.xml      |    8 +-
 .../expectedFiles/webserviceClasspath.xml          |   21 +-
 .../expectedFiles/webserviceWtpComponent.xml       |    4 +
 .../src/main/java/org/gradle/api/PersonList.java   |    4 +-
 .../canCreateAndDeleteMetaData/master/build.gradle |   96 --
 .../webAppJava6/build.gradle                       |    4 +
 .../main/java/org/gradle/webservice/TestTest.java  |    4 +-
 .../expectedFiles/api/api.iml.xml                  |    2 +-
 .../expectedFiles/webservice/webservice.iml.xml    |   20 +-
 .../expectedFiles/root.iml.xml                     |    4 +-
 .../ide/eclipse/model/AbstractLibrary.groovy       |   16 +
 .../ide/eclipse/model/EclipseClasspath.groovy      |   24 +-
 .../ide/eclipse/model/ProjectDependency.groovy     |   18 +-
 .../eclipse/model/internal/ClasspathFactory.groovy |   11 +-
 .../model/internal/ExportedEntriesUpdater.groovy   |   33 -
 .../model/internal/ProjectDependencyBuilder.groovy |   10 +-
 .../plugins/ide/idea/model/ModuleDependency.groovy |    2 +-
 .../plugins/ide/idea/model/ModuleLibrary.groovy    |    2 +-
 .../internal/tooling/BuildInvocationsBuilder.java  |   15 +-
 .../ide/internal/tooling/EclipseModelBuilder.java  |    4 +-
 .../ide/internal/tooling/GradleProjectBuilder.java |   16 +-
 .../tooling/ToolingModelBuilderSupport.java        |   32 +
 .../eclipse/DefaultEclipseExternalDependency.java  |    9 +-
 .../eclipse/DefaultEclipseProjectDependency.java   |    9 +-
 .../resolver/DefaultIdeDependencyResolver.java     |  100 +-
 .../tooling/model/DefaultBuildInvocations.java     |   45 +
 .../tooling/model/LaunchableGradleProjectTask.java |   32 +
 .../tooling/model/LaunchableGradleTask.java        |  101 ++
 .../model/LaunchableGradleTaskSelector.java        |  100 ++
 .../ide/idea/model/ModuleDependencyTest.groovy     |   12 +-
 .../ide/idea/model/ModuleLibraryTest.groovy        |   12 +-
 subprojects/integ-test/integ-test.gradle           |    4 +
 .../BuildScriptClasspathIntegrationTest.java       |    4 +-
 .../integtests/CustomPluginIntegrationTest.groovy  |    2 +
 .../DistributionLocatorIntegrationTest.groovy      |    4 +-
 ...gacyAndComponentJvmPluginIntegrationTest.groovy |   31 +-
 .../MixedNativeAndJvmProjectIntegrationTest.groovy |   41 +-
 .../integtests/ProjectLayoutIntegrationTest.groovy |    8 +
 ...ssingBinaryCompatibilityCrossVersionSpec.groovy |    3 +
 .../BuildEnvironmentIntegrationTest.groovy         |    8 +-
 .../AutoTestedSamplesCoreIntegrationTest.groovy    |    2 +-
 .../SamplesCustomPluginIntegrationTest.groovy      |    2 +
 ...lesDependencySubstitutionIntegrationTest.groovy |   63 ++
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |    2 +
 ...mplesMultiProjectBuildSrcIntegrationTest.groovy |    4 +-
 .../SamplesWebProjectIntegrationTest.groovy        |    4 +-
 .../samples/UserGuideSamplesRunner.groovy          |   10 +-
 .../internal-integ-testing.gradle                  |    3 +
 .../fixtures/AbstractIntegrationSpec.groovy        |   36 +-
 .../integtests/fixtures/AvailableJavaHomes.java    |  115 +--
 .../fixtures/FluidDependenciesResolveRunner.groovy |   62 ++
 .../ForkScalaCompileInDaemonModeFixture.groovy     |    3 +
 .../PersistentBuildProcessIntegrationTest.groovy   |   38 +
 .../fixtures/daemon/AbstractDaemonFixture.groovy   |  134 +++
 .../fixtures/daemon/DaemonContextParser.java       |   77 ++
 .../integtests/fixtures/daemon/DaemonFixture.java  |   54 +
 .../fixtures/daemon/DaemonIntegrationSpec.groovy   |   59 ++
 .../fixtures/daemon/DaemonLogFileStateProbe.groovy |   84 ++
 .../fixtures/daemon/DaemonLogsAnalyzer.groovy      |   81 ++
 .../daemon/DaemonRegistryStateProbe.groovy         |   40 +
 .../fixtures/daemon/DaemonStateProbe.java          |   21 +
 .../integtests/fixtures/daemon/DaemonsFixture.java |   41 +
 .../integtests/fixtures/daemon/LegacyDaemon.groovy |   56 +
 .../fixtures/daemon/TestableDaemon.groovy          |   60 ++
 .../fixtures/executer/AbstractGradleExecuter.java  |   55 +
 .../fixtures/executer/DaemonGradleExecuter.java    |    6 +
 .../fixtures/executer/ExecutionResult.java         |    6 +-
 .../fixtures/executer/ForkingGradleExecuter.java   |   29 +-
 .../fixtures/executer/GradleExecuter.java          |   22 +-
 .../integtests/fixtures/executer/GradleHandle.java |    3 +-
 .../fixtures/executer/GradleVersions.java          |   30 +
 .../fixtures/executer/InProcessGradleExecuter.java |  112 +-
 .../executer/OutputScrapingExecutionResult.java    |   24 +-
 .../fixtures/jvm/InstalledJvmLocator.java          |   20 +-
 .../versions/ReleasedVersionDistributions.java     |   10 +
 .../test/fixtures/plugin/PluginBuilder.groovy      |    4 +-
 .../server/http/CyclicBarrierHttpServer.java       |   27 +
 .../test/fixtures/server/http/HttpServer.groovy    |    7 +
 .../fixtures/jvm/UbuntuJvmLocatorTest.groovy       |    4 +-
 .../ReleasedVersionDistributionsTest.groovy        |    8 +
 .../test/fixtures/concurrent/BlockTarget.groovy    |    2 +-
 .../test/fixtures/concurrent/ConcurrentSpec.groovy |    2 +
 .../test/fixtures/concurrent/Instants.groovy       |    6 +
 .../file/AbstractTestDirectoryProvider.java        |   66 +-
 .../test/fixtures/file/LeaksFileHandles.java       |   25 +
 .../test/fixtures/file/TestFileHelper.groovy       |    4 +-
 .../main/groovy/org/gradle/util/RedirectStdIn.java |   53 +-
 .../main/groovy/org/gradle/util/Requires.groovy    |   11 +-
 .../groovy/org/gradle/util/TestPrecondition.groovy |   10 +-
 .../gradle/util/TestPreconditionExtension.groovy   |    8 +-
 .../api/publish/ivy/IvyPublishHttpIntegTest.groovy |    4 +-
 .../publish/ivy/IvyPublishHttpsIntegTest.groovy    |    4 +
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |    2 +
 .../IvySingleProjectPublishIntegrationTest.groovy  |   43 +
 .../publisher/DependencyResolverIvyPublisher.java  |    3 +-
 .../api/publish/ivy/plugins/IvyPublishPlugin.java  |    4 +-
 ...actIvyRemoteLegacyPublishIntegrationTest.groovy |    2 +
 .../testing/jacoco/plugins/JacocoPlugin.groovy     |   12 +-
 .../coffeescript/CoffeeScriptBasePlugin.groovy     |   15 +-
 .../plugins/javascript/envjs/EnvJsPlugin.groovy    |   17 +-
 .../plugins/javascript/jshint/JsHintPlugin.groovy  |   20 +-
 .../plugins/javascript/rhino/RhinoPlugin.groovy    |    8 +-
 .../internal/tasks/compile/ApiGroovyCompiler.java  |    9 +-
 .../api/tasks/compile/GroovyCompileOptions.java    |   30 +
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |    3 +-
 ...guageDependencyResolutionIntegrationTest.groovy |   85 ++
 .../java/JavaLanguageIntegrationTest.groovy        |    4 +-
 .../java/JavaSourceSetIntegrationTest.groovy       |  241 +++++
 .../internal/DefaultJavaLanguageSourceSet.java     |   18 +-
 .../internal/DefaultJavaLocalComponentFactory.java |   87 ++
 .../DefaultJavaSourceSetResolveContext.java        |   75 ++
 .../internal/ProjectLibraryDependencyResolver.java |   94 ++
 .../language/java/plugins/JavaLanguagePlugin.java  |  105 +-
 .../DefaultJavaLanguageSourceSetTest.groovy        |  107 ++
 .../DefaultJavaLocalComponentFactoryTest.groovy    |  142 +++
 .../DefaultJavaSourceSetResolveContextTest.groovy  |   58 ++
 .../language/fixtures/TestJavaComponent.groovy     |    5 +
 .../ResourceOnlyJvmLibraryIntegrationTest.groovy   |   34 +-
 .../compile/daemon/CompilerDaemonStarter.java      |    1 +
 .../jvm/IncrementalTestJvmComponent.groovy         |    2 +
 ...AbstractJvmPluginLanguageIntegrationTest.groovy |   81 +-
 ...eLanguageIncrementalBuildIntegrationTest.groovy |   10 +-
 ...anguageIncrementalCompileIntegrationTest.groovy |    8 +-
 .../AbstractNativeLanguageIntegrationTest.groovy   |    2 +
 ...ctNativePreCompiledHeaderIntegrationTest.groovy |  578 +++++++----
 .../DuplicateBaseNamesIntegrationTest.groovy       |    4 +-
 ...yLanguageIncrementalBuildIntegrationTest.groovy |    3 +
 .../AssemblyLanguageIntegrationTest.groovy         |    2 +
 ...CLanguageIncrementalBuildIntegrationTest.groovy |    2 +
 .../language/c/CLanguageIntegrationTest.groovy     |    4 +-
 ...CPreCompiledHeaderSourcesIntegrationTest.groovy |   10 +-
 .../c/CppCallingCLanguageIntegrationTest.groovy    |    2 +
 .../language/c/MixedLanguageIntegrationTest.groovy |    2 +
 ...pLanguageIncrementalBuildIntegrationTest.groovy |    2 +
 ...anguageIncrementalCompileIntegrationTest.groovy |    2 +
 .../language/cpp/CppLanguageIntegrationTest.groovy |    4 +-
 ...pPreCompiledHeaderSourcesIntegrationTest.groovy |   10 +-
 .../NativeLanguageSamplesIntegrationTest.groovy    |   25 +-
 ...CLanguageIncrementalBuildIntegrationTest.groovy |    2 +-
 ...CPreCompiledHeaderSourcesIntegrationTest.groovy |    8 +-
 .../ObjectiveCUnsupportedIntegrationTest.groovy    |    4 +-
 ...pPreCompiledHeaderSourcesIntegrationTest.groovy |    8 +-
 .../ObjectiveCppUnsupportedIntegrationTest.groovy  |    2 +
 ...ResourcesIncrementalBuildIntegrationTest.groovy |    2 +
 .../rc/WindowsResourcesIntegrationTest.groovy      |    2 +
 .../gradle/language/c/plugins/CLangPCHPlugin.java  |   58 --
 .../org/gradle/language/c/plugins/CLangPlugin.java |   13 +-
 .../org/gradle/language/c/plugins/CPlugin.java     |    1 -
 .../java/org/gradle/language/c/tasks/CCompile.java |    4 +-
 .../language/cpp/plugins/CppLangPCHPlugin.java     |   59 --
 .../gradle/language/cpp/plugins/CppLangPlugin.java |   13 +-
 .../org/gradle/language/cpp/plugins/CppPlugin.java |    1 -
 .../org/gradle/language/cpp/tasks/CppCompile.java  |    4 +-
 .../AbstractHeaderExportingDependentSourceSet.java |   15 +-
 .../internal/AbstractNativeCompileSpec.java        |   11 +-
 .../nativeplatform/internal/CompileTaskConfig.java |   39 +-
 .../internal/PCHCompileTaskConfig.java             |   27 +-
 .../internal/SourceCompileTaskConfig.java          |   57 ++
 .../internal/incremental/CompilationFileState.java |    1 +
 .../incremental/CompilationStateSerializer.java    |   39 +-
 .../incremental/DefaultIncrementalCompilation.java |   11 +-
 .../incremental/DefaultSourceIncludes.java         |   81 --
 .../incremental/DefaultSourceIncludesParser.java   |   13 +-
 .../incremental/DefaultSourceIncludesResolver.java |    9 +-
 .../incremental/IncrementalCompilation.java        |    5 -
 .../incremental/IncrementalCompileProcessor.java   |   11 +-
 .../incremental/IncrementalNativeCompiler.java     |   19 +-
 .../incremental/sourceparser/CSourceParser.java    |    9 +-
 .../incremental/sourceparser/DefaultInclude.java   |  104 ++
 .../sourceparser/DefaultSourceIncludes.java        |   97 ++
 .../sourceparser/RegexBackedCSourceParser.java     |   35 +-
 .../tasks/AbstractNativeCompileTask.java           |   50 +-
 .../tasks/AbstractNativeSourceCompileTask.java     |   63 ++
 .../plugins/ObjectiveCLangPCHPlugin.java           |   58 --
 .../objectivec/plugins/ObjectiveCLangPlugin.java   |   13 +-
 .../objectivec/plugins/ObjectiveCPlugin.java       |    1 -
 .../objectivec/tasks/ObjectiveCCompile.java        |    4 +-
 .../plugins/ObjectiveCppLangPCHPlugin.java         |   58 --
 .../plugins/ObjectiveCppLangPlugin.java            |   12 +-
 .../objectivecpp/plugins/ObjectiveCppPlugin.java   |    1 -
 .../objectivecpp/tasks/ObjectiveCppCompile.java    |    4 +-
 .../AbstractNativeComponentPluginTest.groovy       |   20 +-
 .../assembler/plugins/AssemblerPluginTest.groovy   |   14 +-
 .../gradle/language/c/tasks/CCompileTest.groovy    |   16 +-
 .../c/tasks/CPreCompiledHeaderCompileTest.groovy   |   72 ++
 .../language/cpp/tasks/CppCompileTest.groovy       |   11 +
 .../tasks/CppPreCompiledHeaderCompileTest.groovy   |   72 ++
 .../CompilationStateSerializerTest.groovy          |   10 +-
 .../DefaultSourceIncludesParserTest.groovy         |   34 +-
 .../DefaultSourceIncludesResolverTest.groovy       |   10 +-
 .../IncrementalCompileProcessorTest.groovy         |    1 +
 .../sourceparser/DefaultIncludeTest.groovy         |   43 +
 .../sourceparser/DefaultSourceIncludesTest.groovy  |   44 +
 .../RegexBackedCSourceParserTest.groovy            |   77 +-
 .../objectivec/tasks/ObjectiveCCompileTest.groovy  |   83 ++
 .../ObjectiveCPreCompiledHeaderCompileTest.groovy  |   72 ++
 .../tasks/ObjectiveCppCompileTest.groovy           |   83 ++
 ...ObjectiveCppPreCompiledHeaderCompileTest.groovy |   72 ++
 .../org/gradle/language/scala/ScalaPlatform.java   |    2 +
 .../scala/fixtures/TestScalaComponent.groovy       |    5 +
 subprojects/launcher/launcher.gradle               |    6 +-
 .../GradleConfigurabilityIntegrationSpec.groovy    |    4 +-
 .../AbstractContinuousIntegrationTest.groovy       |  206 ++++
 .../ArchivesContinuousIntegrationTest.groovy       |  147 +++
 .../BuildSrcContinuousIntegrationTest.groovy       |   60 ++
 .../CancellationContinuousIntegrationTest.groovy   |   89 ++
 .../Java7RequiringContinuousIntegrationTest.groovy |   24 +
 .../JdkVersionsContinuousIntegrationTest.groovy    |   84 ++
 .../MultiProjectContinuousIntegrationTest.groovy   |  160 +++
 .../SimpleJavaContinuousIntegrationTest.groovy     |  267 +++++
 .../SmokeContinuousIntegrationTest.groovy          |  308 ++++++
 .../jdk7/SymlinkContinuousIntegrationTest.groovy   |  112 ++
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |   19 +-
 .../DaemonHealthLoggingIntegrationTest.groovy      |    6 +-
 .../DaemonInitScriptHandlingIntegrationTest.groovy |    1 +
 ...itialCommunicationFailureIntegrationSpec.groovy |    1 +
 .../launcher/daemon/DaemonIntegrationSpec.groovy   |   58 --
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |    5 +-
 .../DaemonNativeServicesIntegrationTest.groovy     |   34 -
 .../DaemonOutputToggleIntegrationTest.groovy       |    1 +
 ...emonPerformanceMonitoringIntegrationTest.groovy |    7 +-
 .../daemon/DaemonReuseIntegrationTest.groovy       |    2 +
 .../DaemonStartupMessageIntegrationTest.groovy     |    8 +-
 .../DaemonSystemPropertiesIntegrationTest.groovy   |    1 +
 .../daemon/DispachingFailureIntegrationSpec.groovy |    2 +
 .../launcher/daemon/IsolatedDaemonSpec.groovy      |    4 +-
 .../LocaleSupportDaemonIntegrationTest.groovy      |    1 +
 .../ProcessCrashHandlingIntegrationTest.groovy     |    1 +
 .../daemon/SingleUseDaemonIntegrationTest.groovy   |    2 +-
 .../daemon/StoppingDaemonIntegrationSpec.groovy    |    1 +
 .../gradle/launcher/cli/BuildActionsFactory.java   |   98 +-
 .../launcher/cli/CommandLineActionFactory.java     |    2 +-
 .../launcher/cli/ExceptionReportingAction.java     |    2 +-
 .../java/org/gradle/launcher/cli/Parameters.java   |   54 +
 .../gradle/launcher/cli/ParametersConverter.java   |   94 ++
 .../org/gradle/launcher/cli/RunBuildAction.java    |    2 +-
 .../cli/converter/DaemonCommandLineConverter.java  |   10 +
 .../launcher/daemon/bootstrap/DaemonMain.java      |   44 +-
 .../daemon/bootstrap/DaemonOutputConsumer.java     |   43 +-
 .../launcher/daemon/client/DaemonClient.java       |    4 +-
 .../daemon/client/DaemonClientServices.java        |    8 +-
 .../daemon/client/DaemonClientServicesSupport.java |    1 -
 .../daemon/client/DefaultDaemonStarter.java        |   56 +-
 .../client/EmbeddedDaemonClientServices.java       |   22 +-
 .../daemon/client/JvmVersionValidator.java         |   23 +-
 .../daemon/configuration/DaemonParameters.java     |   35 +-
 .../launcher/daemon/server/DaemonServices.java     |   39 +-
 .../daemon/server/DaemonStateCoordinator.java      |    4 +-
 .../launcher/daemon/server/api/HandleStop.java     |   36 +
 .../server/exec/DefaultDaemonCommandExecuter.java  |   32 +-
 .../launcher/daemon/server/exec/ExecuteBuild.java  |    2 +-
 .../server/exec/StopHandlingCommandExecuter.java   |   45 -
 .../launcher/exec/BuildActionParameters.java       |    4 +
 .../org/gradle/launcher/exec/BuildExecuter.java    |   24 +
 .../exec/ContinuousBuildActionExecuter.java        |  167 +++
 .../exec/DefaultBuildActionParameters.java         |   18 +-
 .../exec/InProcessBuildActionExecuter.java         |   22 +-
 .../gradle/launcher/exec/ReportedException.java    |   25 -
 .../internal/impl/DefaultBuildInvocations.java     |   45 -
 .../internal/impl/LaunchableGradleProjectTask.java |   32 -
 .../internal/impl/LaunchableGradleTask.java        |   91 --
 .../impl/LaunchableGradleTaskSelector.java         |  100 --
 .../provider/BuildClientSubscriptions.java         |   52 +
 .../internal/provider/BuildModelAction.java        |   10 +-
 .../provider/ClientProvidedBuildAction.java        |   10 +-
 .../internal/provider/ConnectionScopeServices.java |    4 +-
 .../provider/DaemonBuildActionExecuter.java        |   27 +-
 .../internal/provider/DefaultConnection.java       |   23 +-
 .../provider/InternalCancellationTokenAdapter.java |    7 +-
 .../internal/provider/LauncherServices.java        |   26 +-
 .../internal/provider/ProviderConnection.java      |   66 +-
 .../provider/events/AbstractOperationResult.java   |   25 +
 .../provider/events/AbstractProgressEvent.java     |   39 +
 .../internal/provider/events/AbstractResult.java   |   48 +
 .../provider/events/AbstractTaskResult.java        |   25 +
 .../provider/events/AbstractTestProgressEvent.java |   39 -
 .../provider/events/AbstractTestResult.java        |   29 +-
 .../provider/events/DefaultFailureResult.java      |   35 +
 .../events/DefaultOperationDescriptor.java         |   55 +
 .../DefaultOperationFinishedProgressEvent.java     |   38 +
 .../DefaultOperationStartedProgressEvent.java      |   30 +
 .../provider/events/DefaultSuccessResult.java      |   24 +
 .../provider/events/DefaultTaskDescriptor.java     |   62 ++
 .../provider/events/DefaultTaskFailureResult.java  |   35 +
 .../events/DefaultTaskFinishedProgressEvent.java   |   38 +
 .../provider/events/DefaultTaskSkippedResult.java  |   33 +
 .../events/DefaultTaskStartedProgressEvent.java    |   30 +
 .../provider/events/DefaultTaskSuccessResult.java  |   33 +
 .../provider/events/DefaultTestDescriptor.java     |    3 +-
 .../provider/events/DefaultTestFailureResult.java  |    7 +-
 .../events/DefaultTestFinishedProgressEvent.java   |    3 +-
 .../provider/events/DefaultTestSkippedResult.java  |    7 +-
 .../events/DefaultTestStartedProgressEvent.java    |    3 +-
 .../provider/events/DefaultTestSuccessResult.java  |    7 +-
 .../launcher/cli/BuildActionsFactoryTest.groovy    |    7 +-
 .../cli/ExceptionReportingActionTest.groovy        |    2 +-
 .../gradle/launcher/cli/RunBuildActionTest.groovy  |    4 +-
 .../bootstrap/DaemonOutputConsumerTest.groovy      |   61 +-
 .../daemon/client/DaemonCancelForwarderTest.groovy |    6 +-
 .../DaemonServerExceptionHandlingTest.groovy       |    6 +-
 .../exec/ContinuousBuildActionExecuterTest.groovy  |  225 +++++
 .../exec/DefaultBuildActionParametersTest.groovy   |    2 +-
 .../exec/InProcessBuildActionExecuterTest.groovy   |   54 +-
 .../internal/provider/ClasspathInfererTest.groovy  |    2 +
 .../provider/DaemonBuildActionExecuterTest.groovy  |    4 +-
 .../daemon/testing/AbstractDaemonFixture.groovy    |  102 --
 .../daemon/testing/DaemonContextParser.java        |   77 --
 .../launcher/daemon/testing/DaemonFixture.java     |   54 -
 .../daemon/testing/DaemonLogFileStateProbe.groovy  |   84 --
 .../daemon/testing/DaemonLogsAnalyzer.groovy       |   81 --
 .../daemon/testing/DaemonRegistryStateProbe.groovy |   40 -
 .../launcher/daemon/testing/DaemonStateProbe.java  |   21 -
 .../launcher/daemon/testing/DaemonsFixture.java    |   41 -
 .../launcher/daemon/testing/LegacyDaemon.groovy    |   57 --
 .../launcher/daemon/testing/TestableDaemon.groovy  |   61 --
 subprojects/maven/maven.gradle                     |    2 +-
 .../maven/MavenPublishBasicIntegTest.groovy        |    3 +
 .../publish/maven/MavenPublishHttpIntegTest.groovy |  154 ++-
 .../maven/MavenPublishHttpsIntegTest.groovy        |    6 +-
 .../maven/MavenPublishIntegrationTest.groovy       |    3 +
 ...MavenPublishNonUniqueSnapshotVersionTest.groovy |   53 +
 .../action/AbstractMavenPublishAction.java         |  206 ++--
 .../action/LoggingMavenTransferListener.java       |   36 +-
 .../maven/internal/action/MavenDeployAction.java   |   88 +-
 .../maven/internal/action/MavenInstallAction.java  |   26 +-
 .../internal/action/MavenWagonDeployAction.java    |    7 +-
 .../maven/internal/action/ParsedMavenPom.java      |   64 --
 .../internal/action/SnapshotVersionManager.java    |   87 ++
 .../internal/deployer/AbstractMavenResolver.java   |    3 +-
 .../maven/internal/pom/DefaultMavenPom.java        |   22 +-
 .../pom/DefaultPomDependenciesConverter.java       |    5 +-
 .../maven/internal/pom/PlexusLoggerAdapter.java    |    4 +
 .../wagon/RepositoryTransportDeployWagon.java      |   11 +-
 .../maven/internal/wagon/WagonRegistry.java        |   45 -
 .../internal/publisher/MavenRemotePublisher.java   |    8 +-
 .../publisher/ValidatingMavenPublisher.java        |    6 +-
 .../internal/tasks/MavenPomFileGenerator.java      |   10 +-
 .../publish/maven/plugins/MavenPublishPlugin.java  |   10 +-
 .../RepositoryTransportDeployWagonTest.groovy      |    2 +-
 .../publisher/ValidatingMavenPublisherTest.groovy  |    2 +-
 subprojects/model-core/model-core.gradle           |    5 +-
 .../model/ConfigurationCycleIntegrationTest.groovy |  105 +-
 .../gradle/model/ModelReuseIntegrationTest.groovy  |   53 +-
 .../ModelRuleBindingFailureIntegrationTest.groovy  |   20 +-
 ...odelRuleBindingValidationIntegrationTest.groovy |    5 +-
 .../model/ModelRuleCachingIntegrationTest.groovy   |   10 +-
 .../ModelRuleValidationIntegrationTest.groovy      |    4 -
 ...ationRuleApplicationOrderIntegrationTest.groovy |   77 +-
 .../model/PluginRuleSourceIntegrationTest.groovy   |   36 +-
 .../model/ScopedRuleSourceIntegrationTest.groovy   |   22 +-
 .../model/TaskCreationIntegrationTest.groovy       |   59 +-
 ...actClassBackedManagedTypeIntegrationTest.groovy |   39 +-
 .../ComplexManagedTypeIntegrationTest.groovy       |   15 +-
 .../CyclicalManagedTypeIntegrationTest.groovy      |   10 +-
 .../EnumsInManagedModelIntegrationTest.groovy      |    3 -
 ...nterfaceBackedManagedTypeIntegrationTest.groovy |   29 +-
 ...validManagedModelMutationIntegrationTest.groovy |   30 +-
 .../InvalidManagedModelRuleIntegrationTest.groovy  |   19 +-
 .../managed/ManagedModelMapIntegrationTest.groovy  |  333 ++++++
 ...odelPropertyTargetingRuleIntegrationTest.groovy |   25 +-
 .../model/managed/ManagedSetIntegrationTest.groovy |   80 +-
 ...anagedTypeImplementationClassCachingSpec.groovy |    5 +-
 ...peWithUnmanagedPropertiesIntegrationTest.groovy |   10 +-
 .../model/managed/ModelSetIntegrationTest.groovy   |  546 ++++++++++
 .../PolymorphicManagedTypeIntegrationTest.groovy   |   25 +-
 .../PrimitivesInManagedModelIntegrationTest.groovy |   44 +-
 .../src/main/java/org/gradle/model/Defaults.java   |    2 +-
 .../src/main/java/org/gradle/model/Managed.java    |   15 +-
 .../src/main/java/org/gradle/model/ModelMap.java   |  172 ++++
 .../src/main/java/org/gradle/model/ModelSet.java   |   61 ++
 .../gradle/model/collection/CollectionBuilder.java |   13 +-
 .../org/gradle/model/collection/ManagedSet.java    |    4 +-
 .../internal/ModelMapModelProjection.java          |  188 ++++
 .../internal/core/ActionBackedModelAction.java     |   59 --
 .../model/internal/core/BaseInstanceFactory.java   |   98 ++
 .../internal/core/BiActionBackedModelAction.java   |   70 --
 .../internal/core/ChainingModelProjection.java     |   29 +-
 .../internal/core/ChildNodeCreatorStrategy.java    |   27 +
 .../internal/core/CollectionBuilderModelView.java  |  283 ------
 .../internal/core/DefaultCollectionBuilder.java    |  314 ------
 .../model/internal/core/DefaultModelViewState.java |   78 ++
 .../internal/core/DelegatingCollectionBuilder.java |  157 ---
 .../core/DirectNodeInputUsingModelAction.java      |   62 ++
 .../model/internal/core/DirectNodeModelAction.java |   70 --
 .../core/DirectNodeNoInputsModelAction.java        |   70 ++
 .../model/internal/core/EmptyModelProjection.java  |   10 +-
 .../model/internal/core/InputUsingModelAction.java |   70 ++
 .../model/internal/core/InstanceFactory.java       |   29 +
 .../model/internal/core/InstanceModelView.java     |   14 +-
 .../model/internal/core/ModelActionRole.java       |   24 +-
 .../gradle/model/internal/core/ModelAdapter.java   |    3 +
 .../gradle/model/internal/core/ModelCreator.java   |    2 +-
 .../gradle/model/internal/core/ModelCreators.java  |  100 +-
 .../internal/core/ModelMapGroovyDecorator.java     |  242 +++++
 .../org/gradle/model/internal/core/ModelNode.java  |   25 +-
 .../org/gradle/model/internal/core/ModelPath.java  |   31 +-
 .../gradle/model/internal/core/ModelPredicate.java |   66 ++
 .../gradle/model/internal/core/ModelPromise.java   |    4 +-
 .../gradle/model/internal/core/ModelReference.java |   66 +-
 .../gradle/model/internal/core/ModelRegistrar.java |   29 -
 .../model/internal/core/ModelViewFactory.java      |   23 +
 .../gradle/model/internal/core/ModelViewState.java |   28 +
 .../model/internal/core/MutableModelNode.java      |   41 +-
 .../internal/core/NamedEntityInstantiators.java    |   28 +
 .../model/internal/core/NoInputsModelAction.java   |   59 ++
 .../model/internal/core/NodeBackedModelMap.java    |  325 ++++++
 .../model/internal/core/NodeBackedModelSet.java    |  182 ++++
 .../core/ProjectionBackedModelCreator.java         |   18 +-
 .../core/SpecializedModelMapProjection.java        |  124 +++
 .../TypeCompatibilityModelProjectionSupport.java   |    4 +-
 .../model/internal/core/TypedModelProjection.java  |   70 ++
 .../internal/core/UnmanagedModelProjection.java    |   19 +
 .../rule/describe/NestedModelRuleDescriptor.java   |    4 +
 .../rule/describe/StandardDescriptorFactory.java   |   38 +
 .../inspect/DefaultModelCreatorFactory.java        |  222 +++-
 .../internal/inspect/ManagedModelInitializer.java  |   19 +-
 .../internal/inspect/ManagedSetInitializer.java    |   40 -
 .../internal/inspect/MethodBackedModelAction.java  |    5 +
 .../UnmanagedModelCreationRuleExtractor.java       |    2 +-
 .../manage/projection/ManagedModelProjection.java  |    6 +
 .../projection/ManagedSetModelProjection.java      |  244 -----
 .../manage/schema/ModelCollectionSchema.java       |   15 +
 .../internal/manage/schema/ModelMapSchema.java     |   40 +
 .../model/internal/manage/schema/ModelSchema.java  |   17 +-
 .../internal/manage/schema/ModelSchemaStore.java   |    2 +
 .../manage/schema/cache/ModelSchemaCache.java      |    2 +-
 .../extract/AbstractProxyClassGenerator.java       |   51 +
 .../schema/extract/DefaultModelSchemaStore.java    |    5 +
 .../InvalidManagedModelElementTypeException.java   |    2 +
 .../schema/extract/JdkValueTypeStrategy.java       |    4 +-
 .../ManagedCollectionProxyClassGenerator.java      |   75 ++
 .../schema/extract/ManagedProxyClassGenerator.java |   38 +-
 .../manage/schema/extract/ManagedSetStrategy.java  |   59 +-
 .../manage/schema/extract/ModelMapStrategy.java    |   83 ++
 .../schema/extract/ModelSchemaExtractor.java       |   15 +-
 .../manage/schema/extract/ModelSetStrategy.java    |   32 +
 .../manage/schema/extract/SetStrategy.java         |   82 ++
 .../schema/extract/SpecializedMapStrategy.java     |   67 ++
 .../manage/schema/extract/StructStrategy.java      |   41 +-
 .../registry/AnyStateBindingPredicate.java         |   33 +
 .../internal/registry/BinderCreationListener.java  |   42 -
 .../model/internal/registry/BindingPredicate.java  |   63 ++
 .../model/internal/registry/CreatorRuleBinder.java |    8 +-
 .../internal/registry/DefaultModelRegistry.java    | 1047 +++++++++++++------
 .../model/internal/registry/ModelBinding.java      |   56 +-
 .../internal/registry/ModelCreationListener.java   |   45 +-
 .../gradle/model/internal/registry/ModelGraph.java |  114 ++-
 .../model/internal/registry/ModelNodeInternal.java |   83 +-
 .../model/internal/registry/ModelRegistry.java     |   18 +-
 .../model/internal/registry/MutatorRuleBinder.java |   53 +-
 .../model/internal/registry/NodeAtState.java       |   61 ++
 .../registry/OneOfTypeBinderCreationListener.java  |   44 +-
 .../registry/PathBinderCreationListener.java       |   33 +-
 .../gradle/model/internal/registry/RuleBinder.java |   94 +-
 .../model/internal/registry/RuleBindings.java      |  187 ++++
 .../model/internal/registry/RuleContext.java       |   64 ++
 .../model/internal/registry/SingleNodeBinding.java |   20 +
 .../internal/registry/UnboundRulesProcessor.java   |   31 +-
 .../report/IncompatibleTypeReferenceReporter.java  |    8 +-
 .../model/internal/type/ClassTypeWrapper.java      |   15 +-
 .../org/gradle/model/internal/type/ModelType.java  |   11 +-
 .../org/gradle/model/internal/type/ModelTypes.java |   34 +-
 .../model/internal/type/NullTypeWrapper.java       |   33 -
 .../internal/type/ParameterizedTypeWrapper.java    |   53 +-
 .../gradle/model/internal/type/TypeWrapper.java    |    2 +-
 .../model/internal/type/WildcardTypeWrapper.java   |   25 +-
 .../gradle/model/ManagedModelMapTypesTest.groovy   |  112 ++
 .../org/gradle/model/ManagedNamedTest.groovy       |   88 ++
 .../model/ManagedNodeBackedModelMapTest.groovy     |  860 ++++++++++++++++
 .../org/gradle/model/NamedThingInterface.java      |   26 +
 .../src/test/groovy/org/gradle/model/Special.java  |   23 +
 .../gradle/model/SpecialNamedThingInterface.java   |   24 +
 .../model/UnmanagedNodeBackedModelMapTest.groovy   |  872 ++++++++++++++++
 .../internal/CollectionBuilderModelViewTest.groovy |   51 -
 .../internal/DefaultCollectionBuilderTest.groovy   |  842 ---------------
 .../collection/internal/HasDependencies.groovy     |   27 -
 .../gradle/model/collection/internal/Special.java  |   23 -
 .../core/ModelMapGroovyDecoratorTest.groovy        |   40 +
 .../model/internal/core/ModelPathTest.groovy       |   50 +-
 .../model/internal/core/ModelReferenceTest.groovy  |  112 ++
 .../model/internal/core/ModelTypeJavaTest.java     |   19 +
 .../model/internal/core/ModelTypeTest.groovy       |   33 +
 .../core/NamedEntityInstantiatorsTest.groovy       |   39 +
 .../describe/StandardDescriptorFactoryTest.groovy  |   38 +
 .../internal/inspect/ModelRuleExtractorTest.groovy |   11 +-
 .../ManagedSetModelProjectionTest.groovy           |  170 ----
 .../projection/ModelSetModelProjectionTest.groovy  |  170 ++++
 .../extract/DefaultModelSchemaStoreTest.groovy     |   14 +-
 ...ManagedCollectionProxyClassGeneratorTest.groovy |   89 ++
 .../schema/extract/ModelSchemaExtractorTest.groovy |  130 ++-
 .../manage/schema/extract/SpecialManagedSet.java   |   21 -
 .../manage/schema/extract/SpecialModelSet.java     |   21 +
 .../registry/DefaultModelRegistryTest.groovy       |  389 ++++++-
 .../model/internal/registry/HasDependencies.groovy |   27 +
 .../model/internal/registry/ModelGraphTest.groovy  |   68 +-
 .../registry/ModelRegistryEphemeralNodeTest.groovy |   68 +-
 .../model/internal/registry/RegistrySpec.groovy    |  275 +++++
 .../internal/registry/RuleBindingsTest.groovy      |  479 +++++++++
 .../model/internal/registry/ScopedRuleTest.groovy  |    1 -
 .../registry/UnboundRulesProcessorTest.groovy      |  229 +----
 .../internal/fixture/ModelRegistryHelper.java      |  178 ++--
 .../dsl/ModelDslCreationIntegrationTest.groovy     |  104 +-
 .../model/dsl/ModelDslIntegrationTest.groovy       |   70 +-
 .../ModelDslRuleDetectionIntegrationSpec.groovy    |    4 +-
 ...odelDslRuleInputDetectionIntegrationSpec.groovy |  152 ++-
 .../NestedModelDslUsageIntegrationSpec.groovy      |    6 +-
 .../internal/NonTransformedModelDslBacking.java    |    4 +-
 .../dsl/internal/TransformedModelDslBacking.java   |   57 +-
 .../model/dsl/internal/inputs/RuleInputAccess.java |    4 +-
 .../internal/inputs/RuleInputAccessBacking.java    |    6 +
 .../dsl/internal/transform/InputReferences.java    |   73 ++
 .../model/dsl/internal/transform/RuleMetadata.java |   18 +-
 .../model/dsl/internal/transform/RuleVisitor.java  |  115 ++-
 .../NonTransformedModelDslBackingTest.groovy       |    4 +-
 .../internal/TransformedModelDslBackingTest.groovy |   11 +-
 .../filesystem/services/FileSystemServices.java    |    2 +-
 subprojects/performance/performance.gradle         |   35 +-
 subprojects/performance/src/generator.groovy       |    4 +
 .../NativePreCompiledHeaderPerformanceTest.groovy  |   46 +
 .../NativeScenarioPerformanceTest.groovy           |    1 -
 .../templates/native-pch-component/build.gradle    |   40 +
 .../src/templates/native-pch-source/lib.c          |    8 +
 .../src/templates/native-pch-source/pch.h          |   10 +
 .../src/templates/project-with-source/build.gradle |    4 +
 .../src/templates/variants-new-model/build.gradle  |    8 +-
 .../performance/results/ReportGeneratorTest.groovy |    2 +
 subprojects/platform-base/platform-base.gradle     |    2 +
 .../base/AssembleTaskIntegrationTest.groovy        |    4 +-
 .../base/ComponentModelIntegrationTest.groovy      |  838 +++++++++++++++
 .../base/ComponentTypeSampleIntegTest.groovy       |   33 +-
 .../base/CustomBinaryIntegrationTest.groovy        |   13 +-
 .../base/CustomBinaryTasksIntegrationTest.groovy   |   44 +-
 .../CustomComponentBinariesIntegrationTest.groovy  |  120 ++-
 .../CustomComponentPluginIntegrationTest.groovy    |  119 ++-
 .../base/LanguageTypeIntegrationTest.groovy        |    7 +-
 .../base/LanguageTypeSampleIntegrationTest.groovy  |    2 +-
 .../ComponentModelBasePluginIntegrationTest.groovy |  131 +++
 .../LifecycleBasePluginIntegrationTest.groovy      |    4 +-
 .../gradle/language/base/FunctionalSourceSet.java  |    4 +-
 .../gradle/language/base/LanguageSourceSet.java    |    6 +-
 .../base/internal/DefaultFunctionalSourceSet.java  |   15 +-
 .../base/internal/DependentSourceSetInternal.java  |   25 +
 .../base/internal/LanguageSourceSetContainer.java  |   11 +-
 .../base/internal/LanguageSourceSetInternal.java   |    4 +
 .../internal/model/BinarySpecFactoryRegistry.java  |   64 ++
 .../base/internal/model/ComponentRules.java        |  105 ++
 .../internal/model/ComponentSpecInitializer.java   |   54 +
 .../base/plugins/ComponentModelBasePlugin.java     |  141 ++-
 .../language/base/plugins/LanguageBasePlugin.java  |   73 +-
 .../language/base/plugins/LifecycleBasePlugin.java |    2 +-
 .../base/sources/BaseLanguageSourceSet.java        |    4 +
 .../core/DomainObjectSetBackedModelMap.java        |  232 +++++
 .../java/org/gradle/platform/base/BinarySpec.java  |   24 +-
 .../java/org/gradle/platform/base/BinaryTasks.java |    7 +-
 .../java/org/gradle/platform/base/BinaryType.java  |    3 -
 .../gradle/platform/base/ComponentBinaries.java    |    5 +-
 .../org/gradle/platform/base/ComponentSpec.java    |    9 +-
 .../platform/base/ComponentSpecContainer.java      |    4 +-
 .../org/gradle/platform/base/ComponentType.java    |    5 +-
 .../org/gradle/platform/base/DependencySpec.java   |   44 +
 .../platform/base/DependencySpecBuilder.java       |   47 +
 .../platform/base/DependencySpecContainer.java     |   46 +
 .../org/gradle/platform/base/LanguageType.java     |    3 -
 .../platform/base/binary/BaseBinarySpec.java       |   11 +-
 .../platform/base/component/BaseComponentSpec.java |  166 ++-
 .../platform/base/internal/BinarySpecFactory.java  |   26 +
 .../platform/base/internal/BinarySpecInternal.java |    4 +
 .../platform/base/internal/ComponentSpecAware.java |   25 +
 .../base/internal/ComponentSpecFactory.java        |   26 +
 .../base/internal/ComponentSpecInternal.java       |    1 +
 .../base/internal/DefaultBinaryContainer.java      |    5 +-
 .../internal/DefaultComponentSpecContainer.java    |   29 -
 .../base/internal/DefaultDependencySpec.java       |  103 ++
 .../internal/DefaultDependencySpecContainer.java   |  134 +++
 ...nnotationDrivenComponentModelRuleExtractor.java |   14 +-
 .../registry/BinaryTasksModelRuleExtractor.java    |   75 +-
 .../registry/BinaryTypeModelRuleExtractor.java     |   43 +-
 .../registry/CollectionBuilderBasedRule.java       |   97 --
 .../ComponentBinariesModelRuleExtractor.java       |   55 +-
 .../ComponentModelBaseServiceRegistry.java         |    9 +-
 .../registry/ComponentTypeModelRuleExtractor.java  |   46 +-
 .../base/internal/registry/ModelMapBasedRule.java  |   98 ++
 .../RuleAwarePolymorphicDomainObjectContainer.java |   52 -
 .../internal/test/DefaultTestSuiteContainer.java   |   28 -
 .../platform/base/test/TestSuiteContainer.java     |    4 +-
 .../ComponentTypeModelRuleExtractorTest.groovy     |   12 +-
 .../LanguageTypeModelRuleExtractorTest.groovy      |    7 +-
 .../plugins/ComponentModelBasePluginTest.groovy    |  283 ------
 .../base/plugins/LifecycleBasePluginTest.groovy    |    3 +-
 .../base/component/BaseComponentSpecTest.groovy    |   34 +-
 .../BinaryTasksModelRuleExtractorTest.groovy       |   24 +-
 .../BinaryTypeModelRuleExtractorTest.groovy        |   12 +-
 .../ComponentBinariesModelRuleExtractorTest.groovy |   28 +-
 ...warePolymorphicDomainObjectContainerTest.groovy |   66 --
 .../base/component/BaseComponentFixtures.groovy    |   44 +
 subprojects/platform-jvm/platform-jvm.gradle       |    1 +
 .../jvm/ComponentReportIntegrationTest.groovy      |   32 +-
 .../gradle/jvm/ModelReuseIntegrationTest.groovy    |   62 ++
 .../JvmComponentPluginIntegrationTest.groovy       |  111 +-
 .../org/gradle/jvm/plugins/JvmComponentPlugin.java |   12 +-
 .../src/main/java/org/gradle/jvm/tasks/Jar.java    |    2 +-
 .../archives/internal/DefaultManifestTest.groovy   |    6 +-
 .../jvm/internal/DefaultJvmLibrarySpecTest.groovy  |   17 +-
 .../internal/plugins/CreateJvmBinariesTest.groovy  |   14 +-
 .../BinaryBuildTypesIntegrationTest.groovy         |    9 +-
 .../BinaryConfigurationIntegrationTest.groovy      |    2 +
 .../BinaryFlavorsIntegrationTest.groovy            |    8 +-
 .../ComponentReportIntegrationTest.groovy          |   20 +-
 .../LibraryApiDependenciesIntegrationTest.groovy   |    6 +-
 .../LibraryBinariesIntegrationTest.groovy          |    2 +
 .../LibraryDependenciesIntegrationTest.groovy      |    2 +
 .../ModelReuseIntegrationTest.groovy               |   85 ++
 .../NativeBinariesIntegrationTest.groovy           |   13 +-
 .../NativePlatformSamplesIntegrationTest.groovy    |    4 +-
 .../PrebuiltLibrariesIntegrationTest.groovy        |    2 +
 .../TestSuiteDefinitionIntegrationSpec.groovy      |  138 +++
 .../TestSuiteModelIntegrationSpec.groovy           |  352 +++++++
 .../BinaryNativePlatformIntegrationTest.groovy     |    4 +-
 .../GeneratedSourcesIntegrationTest.groovy         |    8 +-
 ...rceSetCompileDependenciesIntegrationTest.groovy |    2 +
 .../SourceSetDependenciesIntegrationTest.groovy    |    8 +-
 ...SourceSetLinkDependenciesIntegrationTest.groovy |    3 +-
 .../CommonToolchainCustomizationIntegTest.groovy   |    2 +
 .../GccToolChainDiscoveryIntegrationTest.groovy    |    4 +-
 .../MultipleNativeToolChainIntegrationTest.groovy  |    2 +
 .../NativeToolChainDiscoveryIntegrationTest.groovy |    2 +
 ...sualCppToolChainDiscoveryIntegrationTest.groovy |    9 +-
 .../nativeplatform/DependentSourceSet.java         |   19 +-
 .../internal/DependentSourceSetInternal.java       |   27 +
 .../language/nativeplatform/internal/Include.java  |   23 +
 .../nativeplatform/internal/IncludeType.java       |   21 +
 .../nativeplatform/internal/SourceIncludes.java    |    8 +-
 .../internal/AbstractNativeBinarySpec.java         |   20 +-
 .../internal/AbstractNativeComponentSpec.java      |    3 +-
 .../internal/NativeBinarySpecInternal.java         |    8 +-
 .../internal/NativePlatformResolver.java           |   12 +-
 .../configure/DefaultNativeBinariesFactory.java    |   77 --
 .../internal/configure/NativeBinaries.java         |   93 ++
 .../internal/configure/NativeBinariesFactory.java  |   28 -
 .../internal/configure/NativeBinaryRules.java      |   72 ++
 .../configure/NativeBinarySpecInitializer.java     |   51 -
 .../internal/configure/NativeComponentRules.java   |  169 ++++
 .../configure/NativeComponentSpecInitializer.java  |  109 --
 ...DefaultPreCompiledHeaderTransformContainer.java |   33 -
 .../internal/pch/PchEnabledLanguageTransform.java  |   26 +
 .../pch/PreCompiledHeaderTransformContainer.java   |   23 -
 .../prebuilt/PrebuiltLibraryInitializer.java       |    3 +-
 .../internal/resolve/DefaultLibraryResolver.java   |    5 +-
 .../resolve/LibraryNativeDependencyResolver.java   |    7 +-
 .../resolve/NativeDependencyResolverServices.java  |    5 +-
 .../resolve/ProjectLibraryBinaryLocator.java       |   29 +-
 .../internal/services/NativeBinaryServices.java    |    4 +
 .../platform/internal/NativePlatforms.java         |    4 +-
 .../plugins/NativeComponentModelPlugin.java        |  194 ++--
 .../tasks/PrefixHeaderFileGenerateTask.java        |   16 +-
 .../test/plugins/NativeBinariesTestPlugin.java     |   55 +-
 .../toolchain/internal/NativeCompileSpec.java      |    5 +-
 .../toolchain/internal/NativeCompiler.java         |   46 +-
 .../internal/PCHObjectDirectoryGeneratorUtil.java  |   39 -
 .../toolchain/internal/PCHUtils.java               |  108 ++
 .../toolchain/internal/PlatformToolProvider.java   |    2 -
 .../toolchain/internal/PreCompiledHeader.java      |   58 ++
 .../internal/PrefixHeaderFileGeneratorUtil.java    |   49 -
 .../internal/UnavailablePlatformToolProvider.java  |    7 +-
 .../toolchain/internal/gcc/Assembler.java          |   10 +
 .../internal/gcc/GccCompatibleNativeCompiler.java  |    2 +-
 .../internal/gcc/GccPlatformToolProvider.java      |    1 -
 .../toolchain/internal/msvcpp/Assembler.java       |    5 +-
 .../toolchain/internal/msvcpp/CPCHCompiler.java    |    2 +-
 .../toolchain/internal/msvcpp/CppPCHCompiler.java  |    2 +-
 .../internal/msvcpp/VisualCppNativeCompiler.java   |   11 +-
 .../VisualCppPCHSourceFileGeneratorUtil.java       |   62 --
 .../msvcpp/VisualCppPCHSourceFileTransformer.java  |   36 -
 .../msvcpp/VisualCppPlatformToolProvider.java      |   13 +-
 .../internal/msvcpp/WindowsResourceCompiler.java   |    3 +
 .../internal/DefaultNativeComponentTest.groovy     |    6 +-
 .../DefaultNativeExecutableBinarySpecTest.groovy   |   15 +-
 .../DefaultNativeExecutableSpecTest.groovy         |    6 +-
 .../internal/DefaultNativeLibrarySpecTest.groovy   |    5 +-
 .../DefaultSharedLibraryBinarySpecTest.groovy      |   13 +-
 .../DefaultStaticLibraryBinarySpecTest.groovy      |   12 +-
 .../internal/NativeBinarySpecTest.groovy           |   12 +-
 .../DefaultNativeBinariesFactoryTest.groovy        |  103 --
 .../configure/NativeBinaryRulesTest.groovy         |  109 ++
 .../NativeBinarySpecInitializerTest.groovy         |   99 --
 .../configure/NativeComponentRulesTest.groovy      |  145 +++
 .../NativeComponentSpecInitializerTest.groovy      |  180 ----
 .../resolve/ProjectLibraryBinaryLocatorTest.groovy |   33 +-
 .../plugins/NativeComponentModelPluginTest.groovy  |   47 +-
 .../toolchain/internal/NativeCompilerTest.groovy   |    3 +
 .../toolchain/internal/PCHUtilsTest.groovy         |  118 +++
 .../PrefixHeaderFileGeneratorUtilTest.groovy       |   46 -
 .../toolchain/internal/gcc/CPCHCompilerTest.groovy |   39 +
 .../internal/gcc/CppPCHCompilerTest.groovy         |   39 +
 .../gcc/GccCompatibleNativeCompilerTest.groovy     |    3 +-
 .../internal/gcc/ObjectiveCCompilerTest.groovy     |   39 +
 .../internal/gcc/ObjectiveCPCHCompilerTest.groovy  |   39 +
 .../internal/gcc/ObjectiveCppCompilerTest.groovy   |   39 +
 .../gcc/ObjectiveCppPCHCompilerTest.groovy         |   39 +
 .../internal/msvcpp/CPCHCompilerTest.groovy        |   44 +
 .../internal/msvcpp/CppPCHCompilerTest.groovy      |   46 +
 .../msvcpp/VisualCppNativeCompilerTest.groovy      |   10 +-
 .../VisualCppPCHSourceFileGeneratorUtilTest.groovy |   52 -
 .../VisualCppPCHSourceFileTransformerTest.groovy   |   49 -
 .../fixtures/NativePlatformsTestFixture.java       |    9 +-
 .../fixtures/app/CHelloWorldApp.groovy             |   36 +-
 .../fixtures/app/CPCHHelloWorldApp.groovy          |  186 ----
 .../fixtures/app/CommonHeaderHelloWorldApp.groovy  |   40 +
 .../fixtures/app/CppHelloWorldApp.groovy           |   36 +-
 .../fixtures/app/CppPCHHelloWorldApp.groovy        |  187 ----
 .../fixtures/app/IncrementalHelloWorldApp.java     |    2 +-
 .../fixtures/app/MixedLanguageHelloWorldApp.groovy |    6 +-
 .../fixtures/app/ObjectiveCHelloWorldApp.groovy    |   36 +-
 .../fixtures/app/ObjectiveCPCHHelloWorldApp.groovy |  190 ----
 .../fixtures/app/ObjectiveCppHelloWorldApp.groovy  |   34 +-
 .../app/ObjectiveCppPCHHelloWorldApp.groovy        |  187 ----
 .../fixtures/app/PCHHelloWorldApp.groovy           |   31 -
 .../configure/TestNativeBinariesFactory.java       |   42 +
 .../integtest/PlayPlatformIntegrationTest.groovy   |   19 +
 .../PlayApplicationPluginIntegrationTest.groovy    |   29 +-
 .../PlayCoffeeScriptPluginIntegrationTest.groovy   |    4 +-
 .../PlayJavaScriptPluginIntegrationTest.groovy     |    4 +-
 ...offeeScriptImplementationIntegrationTest.groovy |    4 +
 .../play/tasks/RoutesCompileIntegrationTest.groovy |   74 +-
 .../play/tasks/TwirlCompileIntegrationTest.groovy  |   18 +-
 .../fixtures/app/basicplayapp/test/notATest.yaml   |    7 +
 .../app/playappwithdependencies/test/notATest.yaml |    7 +
 .../DefaultPlayDistributionContainer.java          |    2 +-
 .../gradle/play/plugins/PlayApplicationPlugin.java |   65 +-
 .../play/plugins/PlayCoffeeScriptPlugin.java       |    8 +-
 .../play/plugins/PlayDistributionPlugin.java       |   20 +-
 .../gradle/play/plugins/PlayJavaScriptPlugin.java  |   11 +-
 .../java/org/gradle/play/plugins/PlayPlugin.java   |    2 +-
 .../org/gradle/play/plugins/PlayTestPlugin.java    |   12 +-
 .../main/java/org/gradle/play/tasks/PlayRun.java   |    3 +-
 .../play/plugins/PlayCoffeeScriptPluginTest.groovy |    4 +-
 .../play/plugins/PlayDistributionPluginTest.groovy |    6 +-
 .../play/plugins/PlayJavaScriptPluginTest.groovy   |    4 +-
 .../gradle/play/plugins/PlayTestPluginTest.groovy  |   14 +-
 .../org/gradle/play/tasks/PlayRunTest.groovy       |   10 +-
 .../use/DeployedPortalIntegrationSpec.groovy       |    2 +
 ...readyOnClasspathDetectionIntegrationSpec.groovy |    2 +
 .../NonDeclarativePluginUseIntegrationSpec.groovy  |    2 +
 ...tPluginResolutionFailuresIntegrationSpec.groovy |    4 +-
 .../use/RuleSourcePluginUseIntegrationSpec.groovy  |    2 +
 ...lutionCachingCrossVersionIntegrationTest.groovy |    2 +
 .../PluginResolutionCachingIntegrationTest.groovy  |    2 +
 ...esolutionDeprecatedClientIntegrationTest.groovy |    2 +
 ...ginResolutionServiceCommsIntegrationTest.groovy |    2 +
 .../PluginResolutionServiceIntegrationSpec.groovy  |    2 +
 .../internal/DefaultPluginRequestApplicator.java   |   16 +-
 .../PluginResolutionServiceTestServer.groovy       |    2 +
 .../api/plugins/BasePluginIntegrationTest.groovy   |    2 +
 .../gradle/api/plugins/BuildSrcPluginTest.groovy   |    6 +-
 .../BasicGroovyCompilerIntegrationSpec.groovy      |  110 +-
 .../InProcessGroovyCompilerIntegrationTest.groovy  |    3 +
 .../java/ComponentReportIntegrationTest.groovy     |   10 +-
 .../BasicJavaCompilerIntegrationSpec.groovy        |    4 +-
 .../compile/JavaCompilerIntegrationSpec.groovy     |    3 +
 .../gradle/testing/TestTaskIntegrationTest.groovy  |    8 +-
 .../JUnitConsoleLoggingIntegrationTest.groovy      |   20 +-
 .../TestNGConsoleLoggingIntegrationTest.groovy     |   16 +-
 .../internal/DefaultDistributionContainer.java     |    3 +-
 .../distribution/plugins/DistributionPlugin.groovy |    6 +-
 .../internal/java/AbstractLanguageSourceSet.java   |    1 +
 .../jvm/ClassDirectoryBinarySpecInternal.java      |    3 +
 .../jvm/DefaultClassDirectoryBinarySpec.java       |    7 +
 .../tasks/testing/AbstractTestDescriptor.java      |    8 +
 .../tasks/testing/DecoratingTestDescriptor.java    |   12 +
 .../tasks/testing/TestDescriptorInternal.java      |    7 +
 .../testing/detection/DefaultTestExecuter.java     |   10 +-
 .../tasks/testing/processors/TestMainAction.java   |   22 +-
 .../testing/results/UnknownTestDescriptor.java     |    9 +-
 .../groovy/org/gradle/api/plugins/BasePlugin.java  |    4 +-
 .../org/gradle/api/plugins/JavaBasePlugin.java     |   13 +-
 .../groovy/org/gradle/api/tasks/GroovyRuntime.java |   15 +-
 .../plugins/DistributionPluginTest.groovy          |   15 +-
 .../internal/java/DefaultJavaSourceSetTest.groovy  |    7 +-
 .../tasks/DefaultGroovySourceSetTest.groovy        |    7 +-
 .../detection/DefaultTestExecuterTest.groovy       |    3 +
 .../junit/result/TestOutputStoreSpec.groovy        |   29 +-
 .../testing/logging/SimpleTestDescriptor.groovy    |    1 +
 .../testing/processors/TestMainActionTest.groovy   |   10 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |   11 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |   17 +-
 .../api/publish/plugins/PublishingPlugin.java      |    4 +-
 .../http/AlwaysRedirectRedirectStrategy.java       |   67 ++
 .../resource/transport/http/HttpClientHelper.java  |   18 +-
 .../http/AlwaysRedirectRedirectStrategyTest.groovy |   62 ++
 .../MavenPublishS3ErrorsIntegrationTest.groovy     |    1 +
 .../internal/resource/UriResourceTest.groovy       |    5 +-
 ...ForkingOlderScalaCompilerIntegrationTest.groovy |    2 +
 .../AntForkingScalaCompilerIntegrationTest.groovy  |    4 +-
 ...ProcessOlderScalaCompilerIntegrationTest.groovy |    2 +
 ...AntInProcessScalaCompilerIntegrationTest.groovy |    2 +
 .../ZincScalaCompilerIntegrationTest.groovy        |    4 +-
 .../groovy/org/gradle/api/tasks/ScalaRuntime.java  |    8 +-
 .../tasks/DefaultScalaSourceSetTest.groovy         |   10 +-
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |    7 +-
 .../plugins/sonar/SonarSmokeIntegrationTest.groovy |    2 +
 .../runner/SonarRunnerSmokeIntegrationTest.groovy  |    2 +
 .../gradle/sonar/runner/SonarRunnerExtension.java  |    8 +-
 .../sonar/runner/plugins/SonarRunnerPlugin.java    |   24 +-
 .../org/gradle/sonar/runner/tasks/SonarRunner.java |   12 +-
 .../test/cunit/CUnitIntegrationTest.groovy         |   99 +-
 .../cunit/ComponentReportIntegrationTest.groovy    |   12 +-
 .../ComponentReportIntegrationTest.groovy          |   10 +-
 .../googletest/GoogleTestIntegrationTest.groovy    |   49 +-
 .../internal/DefaultCUnitTestSuiteBinary.java      |   17 -
 .../cunit/internal/DefaultCUnitTestSuiteSpec.java  |    3 +-
 .../test/cunit/plugins/CUnitPlugin.java            |  141 ++-
 .../internal/DefaultGoogleTestTestSuiteBinary.java |   17 -
 .../internal/DefaultGoogleTestTestSuiteSpec.java   |    4 +-
 .../test/googletest/plugins/GoogleTestPlugin.java  |  142 ++-
 .../nativeplatform/test/cunit/CUnitTest.groovy     |   25 +-
 .../test/googletest/GoogleTestTest.groovy          |   23 +-
 .../runner/BuildClientSubscriptionsSetup.java      |   39 +
 .../provider/runner/BuildModelActionRunner.java    |   10 +-
 .../runner/ClientForwardingBuildListener.java      |   68 ++
 .../runner/ClientForwardingTaskListener.java       |   89 ++
 .../runner/ClientForwardingTestListener.java       |   27 +-
 .../runner/ClientProvidedBuildActionRunner.java    |    4 +
 .../ClientProvidedBuildActionRunnerTest.groovy     |   13 +-
 .../SamplesToolingApiIntegrationTest.groovy        |    4 +-
 .../tooling/ToolingApiIntegrationTest.groovy       |    2 +
 .../tooling/ToolingApiRemoteIntegrationTest.groovy |    4 +-
 .../ContinuousBuildToolingApiSpecification.groovy  |  183 ++++
 .../integtests/tooling/fixture/ToolingApi.groovy   |   17 +-
 .../ToolingApiCompatibilitySuiteRunner.groovy      |    4 +
 .../tooling/fixture/ToolingApiSpecification.groovy |   10 +-
 .../tooling/fixture/ToolingApiVersions.java        |   31 +
 .../m8/ToolingApiLoggingCrossVersionSpec.groovy    |   12 +-
 ...ApiInitScriptCrossVersionIntegrationTest.groovy |    2 +
 .../tooling/r22/BuildActionCrossVersionSpec.groovy |    5 +-
 .../DaemonUsageSuggestionCrossVersionTest.groovy   |    8 +-
 .../r24/TestProgressCrossVersionSpec.groovy        |  672 +++++-------
 ...TestProgressDaemonErrorsCrossVersionSpec.groovy |   40 +-
 .../r25/BuildProgressCrossVersionSpec.groovy       |  438 ++++++++
 ...tinuousBuildCancellationCrossVersionSpec.groovy |   97 ++
 .../r25/ContinuousBuildCrossVersionSpec.groovy     |   71 ++
 ...nuousBuildProgressEventsCrossVersionSpec.groovy |   56 +
 ...usUnsupportedJavaVersionCrossVersionSpec.groovy |   52 +
 ...pportedToolingApiVersionCrossVersionSpec.groovy |   67 ++
 .../r25/GradleTaskGetGroupCrossVersionSpec.groovy  |  103 ++
 .../gradle/integtests/tooling/r25/NullAction.java  |   26 +
 .../tooling/r25/ProgressCrossVersionSpec.groovy    |  215 ++++
 .../r25/TaskProgressCrossVersionSpec.groovy        |  594 +++++++++++
 .../r25/TestProgressCrossVersionSpec.groovy        |  770 ++++++++++++++
 ...TestProgressDaemonErrorsCrossVersionSpec.groovy |   73 ++
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |  128 +++
 .../org/gradle/tooling/BuildActionExecuter.java    |   36 +-
 .../java/org/gradle/tooling/BuildLauncher.java     |   27 +-
 .../gradle/tooling/ListenerFailedException.java    |   44 +
 .../org/gradle/tooling/LongRunningOperation.java   |   24 +-
 .../main/java/org/gradle/tooling/ModelBuilder.java |   26 +-
 .../gradle/tooling/events/OperationDescriptor.java |    7 +-
 .../org/gradle/tooling/events/OperationType.java   |   41 +
 .../gradle/tooling/events/ProgressListener.java    |   58 ++
 .../tooling/events/internal/BaseFinishEvent.java   |   40 -
 .../tooling/events/internal/BaseStartEvent.java    |   31 -
 .../events/internal/DefaultFinishEvent.java        |   40 +
 .../internal/DefaultOperationDescriptor.java       |   58 ++
 .../internal/DefaultOperationFailureResult.java    |   54 +
 .../internal/DefaultOperationSuccessResult.java    |   44 +
 .../tooling/events/internal/DefaultStartEvent.java |   31 +
 .../tooling/events/task/TaskFailureResult.java     |   29 +
 .../tooling/events/task/TaskFinishEvent.java       |   44 +
 .../events/task/TaskOperationDescriptor.java       |   32 +
 .../tooling/events/task/TaskOperationResult.java   |   29 +
 .../tooling/events/task/TaskProgressEvent.java     |   36 +
 .../tooling/events/task/TaskSkippedResult.java     |   37 +
 .../gradle/tooling/events/task/TaskStartEvent.java |   29 +
 .../tooling/events/task/TaskSuccessResult.java     |   35 +
 .../task/internal/DefaultTaskFailureResult.java    |   34 +
 .../task/internal/DefaultTaskFinishEvent.java      |   43 +
 .../internal/DefaultTaskOperationDescriptor.java   |   40 +
 .../task/internal/DefaultTaskSkippedResult.java    |   51 +
 .../task/internal/DefaultTaskStartEvent.java       |   37 +
 .../task/internal/DefaultTaskSuccessResult.java    |   39 +
 .../gradle/tooling/events/task/package-info.java   |   20 +
 .../tooling/events/test/TestFailureResult.java     |   12 -
 .../tooling/events/test/TestProgressListener.java  |   46 -
 .../DefaultJvmTestOperationDescriptor.java         |   64 ++
 .../test/internal/DefaultTestFailureResult.java    |   26 +-
 .../test/internal/DefaultTestFinishEvent.java      |    4 +-
 .../internal/DefaultTestOperationDescriptor.java   |   32 +
 .../test/internal/DefaultTestStartEvent.java       |    4 +-
 .../test/internal/DefaultTestSuccessResult.java    |   19 +-
 .../consumer/AbstractLongRunningOperation.java     |   31 +-
 .../consumer/DefaultBuildActionExecuter.java       |    4 +-
 .../internal/consumer/DefaultBuildLauncher.java    |    4 +-
 .../consumer/DefaultCancellationTokenSource.java   |    2 +-
 .../internal/consumer/DefaultModelBuilder.java     |    4 +-
 .../internal/consumer/ResultHandlerAdapter.java    |    8 +-
 .../parameters/BuildProgressListenerAdapter.java   |  336 +++---
 .../parameters/ConsumerOperationParameters.java    |   38 +-
 .../FailsafeBuildProgressListenerAdapter.java      |   55 +
 .../protocol/InternalBuildProgressListener.java    |   17 +
 .../protocol/events/InternalFailureResult.java     |   25 +
 .../events/InternalOperationDescriptor.java        |   53 +
 .../InternalOperationFinishedProgressEvent.java    |   31 +
 .../protocol/events/InternalOperationResult.java   |   50 +
 .../InternalOperationStartedProgressEvent.java     |   25 +
 .../protocol/events/InternalProgressEvent.java     |   45 +
 .../protocol/events/InternalSuccessResult.java     |   25 +
 .../protocol/events/InternalTaskDescriptor.java    |   31 +
 .../protocol/events/InternalTaskFailureResult.java |   25 +
 .../protocol/events/InternalTaskResult.java        |   25 +
 .../protocol/events/InternalTaskSkippedResult.java |   28 +
 .../protocol/events/InternalTaskSuccessResult.java |   31 +
 .../protocol/events/InternalTestDescriptor.java    |   31 +-
 .../protocol/events/InternalTestFailureResult.java |    2 +-
 .../events/InternalTestFinishedProgressEvent.java  |    2 +-
 .../protocol/events/InternalTestProgressEvent.java |   16 +-
 .../protocol/events/InternalTestResult.java        |   28 +-
 .../events/InternalTestStartedProgressEvent.java   |    4 +-
 .../protocol/events/InternalTestSuccessResult.java |    2 +-
 .../gradle/tooling/model/ExternalDependency.java   |    8 +
 .../main/java/org/gradle/tooling/model/Task.java   |   11 +
 .../tooling/model/build/BuildEnvironment.java      |    3 +-
 .../tooling/model/build/JavaEnvironment.java       |   11 +-
 .../model/eclipse/EclipseProjectDependency.java    |    8 +
 ...CancellableConsumerConnectionAdapterTest.groovy |    4 +-
 ...essListenerAdapterForBuildOperationsTest.groovy |  357 +++++++
 ...ressListenerAdapterForTaskOperationsTest.groovy |  366 +++++++
 ...ressListenerAdapterForTestOperationsTest.groovy |  533 ++++++++++
 .../BuildProgressListenerAdapterTest.groovy        |  553 ++--------
 .../tooling/fixture/TestOutputStream.groovy        |    6 +
 subprojects/tooling-api/tooling-api.gradle         |    2 +-
 .../org/gradle/integtests/OpenApiUiTest.groovy     |    2 +
 .../wrapper/SystemPropertiesHandlerTest.groovy     |    4 +-
 version.txt                                        |    2 +-
 1585 files changed, 51694 insertions(+), 23031 deletions(-)

diff --git a/build.gradle b/build.gradle
index e27d571..5ef32b2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-import org.gradle.build.Install
+
 import org.gradle.build.BuildTypes
+import org.gradle.build.Install
 
 defaultTasks 'assemble'
 apply plugin: 'java-base'
@@ -31,7 +32,7 @@ buildTypes {
     quickCheck "doc:checkstyleApi", "docs:check", "codeQuality", "test"
 
     // For testing pull requests
-    pullRequestValidation "doc:checkstyleApi", "docs:check", "codeQuality"
+    pullRequestValidation "doc:checkstyleApi", "docs:check", "codeQuality", "core:test", "dependencyManagement:test"
 
     // A full (in-process) test
     developerBuild "check"
@@ -65,6 +66,9 @@ buildTypes {
 
     // Used to build production distros and smoke test them
     promotionBuild "verifyIsProductionBuildEnvironment", "clean", "docs:check", "buildDists", "distributions:integTest", "uploadArchives"
+
+    //Initial smoke test for java 9
+    java9Build "java9Test", "java9IntegTest", useIncomingDistributions: true
 }
 
 ext {
@@ -139,6 +143,7 @@ subprojects {
     if (project in groovyProjects) {
         apply from: "$rootDir/gradle/groovyProject.gradle"
         apply from: "$rootDir/gradle/testWithUnknownOS.gradle"
+        apply from: "$rootDir/gradle/java9.gradle"
         check.dependsOn ":docs:checkstyleApi"
         check.dependsOn "codeQuality"
     }
@@ -214,4 +219,4 @@ task installAll(type: Install) {
 }
 
 
-apply from: "gradle/intTestImage.gradle"
\ No newline at end of file
+apply from: "gradle/intTestImage.gradle"
diff --git a/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy b/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy
index f289a90..54230be 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/ReleasedVersions.groovy
@@ -27,6 +27,7 @@ class ReleasedVersions {
 
     private lowestInterestingVersion = GradleVersion.version("0.8")
     private def versions
+    private def snapshots
 
     File destFile
     String url = "https://services.gradle.org/versions/all"
@@ -35,6 +36,7 @@ class ReleasedVersions {
     void prepare() {
         download()
         versions = calculateVersions()
+        snapshots = calculateSnapshots()
     }
 
     private void download() {
@@ -72,10 +74,18 @@ class ReleasedVersions {
         return versions.findAll { it.rcFor == "" }.first().version.version
     }
 
+    String getMostRecentSnapshot() {
+        return snapshots.first().version.version
+    }
+
     List<String> getAllVersions() {
         return versions*.version*.version
     }
 
+    List<String> getAllSnapshots() {
+        return snapshots*.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
@@ -94,4 +104,23 @@ class ReleasedVersions {
 
         return versions
     }
+
+    List<Map<String, ?>> calculateSnapshots() {
+        def snapshots = new groovy.json.JsonSlurper().parseText(destFile.text).findAll {
+            (it.snapshot == true || it.nightly == true) && it.broken == false
+        }.collect {
+            it.version = GradleVersion.version(it.version)
+            it
+        }.findAll {
+            it.version >= lowestInterestingVersion
+        }.sort {
+            it.version
+        }.reverse()
+
+        if (snapshots.size() < 1) {
+            throw new IllegalStateException("Too few snapshots found in ${destFile}: " + versions)
+        }
+
+        return snapshots
+    }
 }
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index f84a243..e501566 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -37,5 +37,9 @@
     <suppress checks="Javadoc.*"
               files=".*[/\\]subprojects[/\\]core[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]logging[/\\]LogLevel.java"/>
 
+    <!-- Protocol types may only define constants in interfaces -->
+    <suppress checks="InterfaceIsTypeCheck"
+              files=".*[/\\]subprojects[/\\]tooling-api[/\\]src[/\\]main[/\\]java[/\\]org[/\\]gradle[/\\]tooling[/\\]internal[/\\]protocol.+"/>
+
 
 </suppressions>
\ No newline at end of file
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 0cee389..f1b6ec4 100755
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -25,8 +25,8 @@ versions.groovy = "2.3.10"
 
 versions.bouncycastle = "1.51"
 
-libraries.ant = dependencies.module('org.apache.ant:ant:1.9.4') {
-    dependency 'org.apache.ant:ant-launcher:1.9.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:5.0.3'
@@ -76,22 +76,6 @@ libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:ht
     dependency "org.samba.jcifs:jcifs:1.3.17 at jar"
 }
 
-libraries.maven_publish = [
-    'classworlds:classworlds:1.1-alpha-2 at jar',
-    'org.codehaus.plexus:plexus-container-default:1.0-alpha-9-stable-1 at jar',
-    'org.codehaus.plexus:plexus-utils:1.5.15 at jar',
-    'org.codehaus.plexus:plexus-interpolation:1.11 at jar',
-    'org.apache.maven:maven-artifact:2.2.1 at jar',
-    'org.apache.maven:maven-artifact-manager:2.2.1 at jar',
-    'org.apache.maven:maven-repository-metadata:2.2.1 at jar',
-    'org.apache.maven:maven-model:2.2.1 at jar',
-    'org.apache.maven:maven-project:2.2.1 at jar',
-    'org.apache.maven:maven-error-diagnostics:2.2.1 at jar',
-    'org.apache.maven:maven-settings:2.2.1 at jar',
-    'org.apache.maven.wagon:wagon-file:1.0-beta-6 at jar',
-    'org.apache.maven.wagon:wagon-http-lightweight:1.0-beta-6 at jar',
-    'org.apache.maven.wagon:wagon-provider-api:1.0-beta-6 at jar',
-]
 libraries += [
         dom4j: 'dom4j:dom4j:1.6.1 at jar',
         guava: 'com.google.guava:guava-jdk5:17.0 at jar',
@@ -127,16 +111,17 @@ libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
 
     //core:
     dependency "org.apache.maven:maven-core:3.0.4 at jar"
+    dependency "org.apache.maven:maven-compat:3.0.4 at jar"
     dependency "org.apache.maven:maven-model-builder:3.0.4 at jar"
     dependency "org.apache.maven:maven-model:3.0.4 at jar"
 
     //somewhat core:
     dependency "org.apache.maven:maven-artifact:3.0.4 at jar"
-    dependency "org.apache.maven:maven-compat:3.0.4 at jar"
     dependency "org.apache.maven:maven-repository-metadata:3.0.4 at jar"
     dependency "org.apache.maven:maven-plugin-api:3.0.4 at jar"
     dependency "org.apache.maven:maven-aether-provider:3.0.4 at jar"
 
+    dependency 'org.apache.maven.wagon:wagon-file:2.4 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'
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
index a17b6a8..05991c9 100644
--- a/gradle/groovyProject.gradle
+++ b/gradle/groovyProject.gradle
@@ -34,7 +34,7 @@ if (!javaVersion.java6Compatible) {
         groovy.exclude '**/jdk6/**'
     }
 }
-if (!javaVersion.java7) {
+if (!javaVersion.java7Compatible) {
     sourceSets.all {
         java.exclude '**/jdk7/**'
         groovy.exclude '**/jdk7/**'
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
index 9dbe8e1..9ac80dd 100644
--- a/gradle/idea.gradle
+++ b/gradle/idea.gradle
@@ -56,7 +56,7 @@ idea {
                 }
                 Collection sourceDirs = groovyProjects.collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
                 sourceDirs.each { sourceFolder ->
-                    if (!javaVersion.java7) {
+                    if (!javaVersion.java7Compatible) {
                         excludeSource("jdk7", sourceFolder)
                     }
                     if (!javaVersion.java6Compatible) {
@@ -114,7 +114,7 @@ idea {
 
                 // Code formatting options
                 def codeFormatSettings = new XmlParser().parseText('''
-          <component name="CodeStyleSettingsManager">
+          <component name="ProjectCodeStyleSettingsManager">
             <option name="PER_PROJECT_SETTINGS">
               <value>
                 <option name="USE_SAME_INDENTS" value="true" />
@@ -130,86 +130,14 @@ idea {
                 <option name="DOWHILE_BRACE_FORCE" value="3" />
                 <option name="WHILE_BRACE_FORCE" value="3" />
                 <option name="FOR_BRACE_FORCE" value="3" />
-                <ADDITIONAL_INDENT_OPTIONS fileType="groovy">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="java">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="js">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="jsp">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="php">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="scala">
-                  <option name="INDENT_SIZE" value="2" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="2" />
-                  <option name="TAB_SIZE" value="2" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="sql">
-                  <option name="INDENT_SIZE" value="2" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
-                <ADDITIONAL_INDENT_OPTIONS fileType="xml">
-                  <option name="INDENT_SIZE" value="4" />
-                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
-                  <option name="TAB_SIZE" value="4" />
-                  <option name="USE_TAB_CHARACTER" value="false" />
-                  <option name="SMART_TABS" value="false" />
-                  <option name="LABEL_INDENT_SIZE" value="0" />
-                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-                  <option name="USE_RELATIVE_INDENTS" value="false" />
-                </ADDITIONAL_INDENT_OPTIONS>
+                <codeStyleSettings language="JAVA">
+                  <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+                  <option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
+                  <option name="IF_BRACE_FORCE" value="3" />
+                  <option name="DOWHILE_BRACE_FORCE" value="3" />
+                  <option name="WHILE_BRACE_FORCE" value="3" />
+                  <option name="FOR_BRACE_FORCE" value="3" />
+                </codeStyleSettings>
               </value>
             </option>
             <option name="USE_PER_PROJECT_SETTINGS" value="true" />
@@ -225,6 +153,10 @@ idea {
                       <option name="heapSize" value="2000" />
                     </component>
                 '''))
+
+                node.append(new NodeBuilder().component(name: 'FrameworkDetectionExcludesConfiguration') {
+                  type(id: 'web')
+                })
             }
         }
     }
@@ -234,6 +166,7 @@ idea {
 
         node.append(new XmlParser().parseText('''
             <component name="CompilerWorkspaceConfiguration">
+              <option name="COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS" value="-Dgroovyc.in.process=false" />
               <option name="USE_COMPILE_SERVER" value="false" />
             </component>
         '''))
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index 473ba0e..6a0a25c 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -1,3 +1,5 @@
+import java.util.regex.Pattern
+
 apply plugin: 'java'
 
 sourceSets {
@@ -17,22 +19,21 @@ dependencies {
 
     //so that implicit help tasks are available:
     integTestRuntime project(':diagnostics')
+
+    //So that the wrapper and init task are added when integTests are run via commandline
+    integTestRuntime project(':buildInit')
     //above can be removed when we implement the auto-apply plugins
 }
 
-ext.integTestTasks = tasks.withType(Test).matching { it.name != 'test' }
-
-import org.gradle.util.GradleVersion
-
-import java.util.regex.Pattern
+ext.integTestTasks = tasks.withType(Test).matching { !['test', 'java9Test'].contains(it.name) }
 
 def removeDodgyCacheFiles(File dir) {
     if (dir.directory) {
-        for (File cacheDir: dir.listFiles()) {
+        for (File cacheDir : dir.listFiles()) {
             if (!cacheDir.name.matches("\\d+\\.\\d+(\\.\\d+)?(-\\w+)*(-\\d{14}[+-]\\d{4})?")) {
                 continue
             }
-            for (String name: ["fileHashes", "outputFileStates", "fileSnapshots"]) {
+            for (String name : ["fileHashes", "outputFileStates", "fileSnapshots"]) {
                 def stateDir = new File(cacheDir, name)
                 if (stateDir.directory) {
                     println "Removing old cache directory : ${stateDir}"
@@ -46,7 +47,7 @@ def removeDodgyCacheFiles(File dir) {
 def removeOldVersionsFromDir(File dir, def shouldDelete, def dirPrefix = "", def dirSuffix = "") {
     if (dir.directory) {
 
-        for (File cacheDir: dir.listFiles()) {
+        for (File cacheDir : dir.listFiles()) {
             if (!cacheDir.name.startsWith(dirPrefix) || !cacheDir.name.endsWith(dirSuffix)) {
                 continue
             }
@@ -110,47 +111,81 @@ project(":") {
     }
 }
 
+def forEachJavaProcess(Closure action) {
+    String queryString = "(?i)${Pattern.quote(File.separator)}(java(?:\\.exe)?.+?(?:(?:-cp.+${Pattern.quote(rootProject.projectDir.absolutePath)}.+?org\\.gradle\\.)|(?:-classpath.+${Pattern.quote(rootProject.buildDir.absolutePath)}.+?org\\.gradle\\.)).+)"
+    def output = new ByteArrayOutputStream()
+    def pidPattern
+    if (org.gradle.internal.os.OperatingSystem.current().windows) {
+        exec {
+            commandLine('wmic', 'process' ,'get', 'processid,commandline')
+            standardOutput = output
+        }
+        pidPattern = /([0-9]+)\s*$/
+    } else {
+        exec {
+            commandLine('ps', 'x')
+            standardOutput = output
+        }
+        pidPattern = /([0-9]+)/
+    }
+    output.toString().readLines().each { String line ->
+        def processMatcher = line =~ queryString
+        if (processMatcher.find()) {
+            def pidMatcher = line =~pidPattern
+            if (pidMatcher.find()) {
+                def pid = pidMatcher.group(1)
+                def process = processMatcher.group(1)
+                action.call(pid, process)
+            }
+        }
+    }
+}
+
+def pkill(pid) {
+    def killOutput = new ByteArrayOutputStream()
+    def result = exec {
+        if (org.gradle.internal.os.OperatingSystem.current().windows) {
+            commandLine = ["taskkill.exe", "/F", "/T", "/PID", pid]
+        } else {
+            commandLine = ["kill", pid]
+        }
+        standardOutput = killOutput
+        errorOutput = killOutput
+        ignoreExitValue = true
+    }
+    if (result.exitValue != 0) {
+        String out = killOutput.toString()
+        if (!out.contains('No such process')) {
+            logger.warn("""Failed to kill daemon process $pid. Maybe already killed?
+Output: ${killOutput}
+""")
+        }
+    }
+}
+
 project(":") {
     if (tasks.findByName('cleanUpDaemons')) {
         return
     }
-    task cleanUpDaemons {
-        onlyIf { !org.gradle.internal.jvm.Jvm.current().ibmJvm }
+    task killExistingDaemons {
         doLast {
-            def output = new ByteArrayOutputStream()
-            exec {
-                commandLine(org.gradle.internal.jvm.Jvm.current().getExecutable("jps").absolutePath, "-m")
-                standardOutput = output
+            forEachJavaProcess { pid, process ->
+                logger.warn("A process wasn't shutdown properly in a previous Gradle run. Killing process with PID $pid (Command line: $process)")
+                pkill(pid)
             }
-            def pattern = Pattern.compile("(\\d+)\\s+GradleDaemon\\s+(\\S+)\\s+(.+)")
-            output.toString().readLines().each { String line ->
-                def matcher = pattern.matcher(line)
-                if (!matcher.matches()) {
-                    return
-                }
-                def remainder = matcher.group(3)
-                if (!remainder.startsWith("${project.projectDir}${File.separator}")) {
-                    return
-                }
-                def pid = matcher.group(1)
-                println "killing daemon with pid $pid"
-                def killOutput = new ByteArrayOutputStream()
-                def result = exec {
-                    if (org.gradle.internal.os.OperatingSystem.current().windows) {
-                        commandLine = ["taskkill.exe", "/F", "/T", "/PID", pid]
-                    } else {
-                        commandLine = ["kill", pid]
-                    }
-                    standardOutput = killOutput
-                    errorOutput = killOutput
-                    ignoreExitValue = true
-                }
-                if (result.exitValue != 0) {
-                    String out = killOutput.toString()
-                    if (!out.contains('No such process')) {
-                        throw new GradleException("""Failed to kill daemon process $pid.
-Output: ${killOutput}
-""")
+        }
+    }
+
+    task cleanUpDaemons {
+        ext.suspiciousDaemons = [:].withDefault {[].asSynchronized()}.asSynchronized()
+        ext.daemonPids = ([] as Set).asSynchronized()
+        doLast {
+            Set alreadyKilled = []
+            forEachJavaProcess { pid, process ->
+                suspiciousDaemons.each { suite, pids ->
+                    if (pids.contains(pid) && !alreadyKilled.contains(pid)) {
+                        logger.warn("A process was created in $suite but wasn't shutdown properly. Killing PID $pid (Command line: $process)")
+                        pkill(pid)
                     }
                 }
             }
@@ -158,11 +193,17 @@ Output: ${killOutput}
     }
 }
 
+allprojects {
+    tasks.whenTaskAdded {
+        it.dependsOn(':killExistingDaemons')
+    }
+}
+
 integTestTasks.all { Test task ->
     dependsOn ':intTestImage', ':cleanUpCaches'
     finalizedBy ':cleanUpDaemons'
     shouldRunAfter 'test'
-    
+
     testClassesDir = sourceSets.integTest.output.classesDir
     classpath = sourceSets.integTest.runtimeClasspath
     testSrcDirs = []
@@ -183,7 +224,7 @@ integTestTasks.all { Test task ->
             systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.destinationDir.absolutePath
             systemProperties['integTest.gradleUserHomeDir'] = rootProject.file('intTestHomeDir').absolutePath
             systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
-            
+
             // If the projects int test need the distributions, they should add:
             // inputs.files rootProject.buildDists
             systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
@@ -193,11 +234,45 @@ integTestTasks.all { Test task ->
             systemProperties['org.gradle.integtest.daemon.registry'] = file("$rootProject.buildDir/daemon").absolutePath
         }
     }
+
+    ext.daemonListener = null
+
+    doFirst {
+        def daemonPids = rootProject.cleanUpDaemons.daemonPids
+        def suspiciousDaemons = rootProject.cleanUpDaemons.suspiciousDaemons
+        daemonListener = [
+            beforeTest : {test -> },
+            afterTest  : { test, result ->},
+            beforeSuite: { suite ->
+                forEachJavaProcess { pid, process ->
+                    // processes that exist before the test suite execution should
+                    // not trigger a warning
+                    daemonPids << pid
+                }
+            },
+            afterSuite : { suite, result ->
+                forEachJavaProcess { pid, process ->
+                    if (!daemonPids.contains(pid)) {
+                        daemonPids << pid
+                        suspiciousDaemons."$suite" << pid
+                    }
+                }
+            }] as TestListener
+        gradle.addListener(daemonListener)
+    }
+
+    doLast {
+        gradle.removeListener(daemonListener)
+    }
 }
 
 task integTest(type: Test) {
     def defaultExecuter = project.hasProperty("defaultIntegTestExecuter") ? project.defaultIntegTestExecuter : "embedded"
     systemProperties['org.gradle.integtest.executer'] = defaultExecuter
+    if (project.hasProperty('org.gradle.integtest.debug')) {
+        systemProperties['org.gradle.integtest.debug'] = 'true'
+        testLogging.showStandardStreams = true
+    }
 }
 check.dependsOn(integTest)
 
diff --git a/gradle/java9.gradle b/gradle/java9.gradle
new file mode 100644
index 0000000..41ee991
--- /dev/null
+++ b/gradle/java9.gradle
@@ -0,0 +1,68 @@
+/**
+ * Tests tasks to allow the gradual introduction of JDK 9 support
+ */
+String jdkVarName = 'JAVA_9'
+
+task java9IntegTest(type: Test) {
+    include '**/*CompilerIntegrationTest*'
+    excludes = [
+        'InProcessGroovyCompilerIntegrationTest',
+        'InProcessJavaCompilerIntegrationTest',
+        'AntForkingScalaCompilerIntegrationTest',
+        'AntInProcessScalaCompilerIntegrationTest',
+        'ZincScalaCompilerIntegrationTest'
+    ].collect { "**/*${it}*" }
+}
+
+task java9Test(type: Test) {
+    excludes = [
+        "JdkToolsTest",
+        "JvmTest",
+        "DefaultIsolatedAntBuilderTest",
+        "DefaultModelSchemaStoreTest",
+        "DefaultJavaToolChainTest",
+        "ModelRuleExtractorTest",
+        "SonarPluginTest",
+        "FunctionalReleaseNotesTest",
+        "StaticReleaseNotesTest",
+        "AssemblerPluginTest",
+        "CPluginTest",
+        "CppPluginTest",
+        "ObjectiveCPluginTest",
+        "ObjectiveCppPluginTest",
+        "ABrokenJunit3TestClass",
+        "ABrokenTestClass",
+        "ATestClassWhichCannotBeLoaded",
+        "ATestClassWithBrokenBeforeAndAfterMethod",
+        "ATestClassWithBrokenBeforeClassMethod",
+        "ATestClassWithBrokenBeforeMethod",
+        "ATestClassWithBrokenConstructor",
+        "ATestClassWithBrokenRunner",
+        "ATestClassWithRunner",
+        "ATestClassWithRunnerThatBreaksAfterRuningSomeTests",
+        "ATestClassWithSeveralMethods",
+        "ATestClassWithUnconstructableRunner"
+    ].collect { "**/*${it}*" }
+}
+
+tasks.withType(Test).matching { it.name.startsWith("java9") }.all {
+    doFirst {
+        jvmArgs = []
+    }
+    executable = "${System.getenv('JAVA_9')}/bin/java"
+    reports.junitXml.destination = file("${project.testResultsDir}/$name")
+    reports.html.destination = file("${project.reporting.baseDir}/$name")
+}
+
+if (!gradle.hasProperty("haveInstalledJava9Guard")) {
+    gradle.taskGraph.whenReady { graph ->
+        if (gradle.taskGraph.allTasks.any { it.name.startsWith("java9") }) {
+            // Ideally, this would be a validate rule but it's not convenient to express this in the DSL yet
+            if (!System.getenv(jdkVarName)) {
+                throw new GradleException("A '$jdkVarName' environment variable, " +
+                    "pointing to a java 9 JDK image, is required to run java9 tests!")
+            }
+        }
+    }
+    gradle.ext.haveInstalledJava9Guard = true
+}
diff --git a/gradle/strictCompile.gradle b/gradle/strictCompile.gradle
index d076a2b..b070602 100644
--- a/gradle/strictCompile.gradle
+++ b/gradle/strictCompile.gradle
@@ -20,4 +20,10 @@ allprojects {
             options.compilerArgs << "-Werror" << "-Xlint:all" << "-Xlint:-options" << "-Xlint:-serial"
         }
     }
-}
\ No newline at end of file
+    ext.strictCompileIgnoreDeprecations = {
+        strictCompile()
+        tasks.withType(AbstractCompile) {
+            options.compilerArgs << "-Xlint:-deprecation"
+        }
+    }
+}
diff --git a/gradle/testFixtures.gradle b/gradle/testFixtures.gradle
index 4a7515f..60bce3c 100644
--- a/gradle/testFixtures.gradle
+++ b/gradle/testFixtures.gradle
@@ -1,9 +1,9 @@
 /*
-    Adds a new testFixtures source set which should contain utilities/fixtures to assist in unit testing 
+    Adds a new testFixtures source set which should contain utilities/fixtures to assist in unit testing
     classes from the main source set.
-    
+
     The test fixtures are automatically made available to the test classpath.
-    
+
     The gradle/groovyProject.gradle script automatically applies this if a project has a src/testFixtures dir.
 */
 apply plugin: 'java'
@@ -13,10 +13,10 @@ configurations {
 
     testFixturesCompile.extendsFrom compile
     testFixturesRuntime.extendsFrom runtime, testFixturesCompile
-        
+
     // Expose configurations that include the test fixture classes for clients to use
     testFixturesUsageCompile.extendsFrom testFixturesCompile, outputDirs
-    testFixturesUsageRuntime.extendsFrom testFixturesRuntime
+    testFixturesUsageRuntime.extendsFrom testFixturesRuntime, testFixturesUsageCompile
 
     // Assume that the project wants to use the fixtures for its tests
     testCompile.extendsFrom testFixturesUsageCompile
diff --git a/gradle/wrapper.gradle b/gradle/wrapper.gradle
index 7e5636d..007ebde 100644
--- a/gradle/wrapper.gradle
+++ b/gradle/wrapper.gradle
@@ -33,7 +33,7 @@ def wrapperUpdateTask = { name, label ->
 
     task "${wrapperTaskName}"(type: Wrapper, dependsOn: configureWrapperTaskName) {
         group = "wrapper"
-		def jvmOpts = "-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"
+		def jvmOpts = "-Xmx1024m -Dfile.encoding=UTF-8"
         inputs.property("jvmOpts", jvmOpts)        
         doLast {
             def optsEnvVar = "DEFAULT_JVM_OPTS"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f21bba9..39fb503 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 07 09:01:34 AEST 2015
+#Tue May 05 11:22:48 CEST 2015
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-2.4-20150417030942+0000-bin.zip
+distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-2.5-20150528195112+0000-bin.zip
diff --git a/gradlew b/gradlew
index e329155..fda6271 100755
--- a/gradlew
+++ b/gradlew
@@ -7,7 +7,7 @@
 ##############################################################################
 
 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS="-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"
+DEFAULT_JVM_OPTS="-Xmx1024m -Dfile.encoding=UTF-8"
 
 APP_NAME="Gradle"
 APP_BASE_NAME=`basename "$0"`
diff --git a/gradlew.bat b/gradlew.bat
index 11192ca..095edf3 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -9,7 +9,7 @@
 if "%OS%"=="Windows_NT" setlocal
 
 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8
+set DEFAULT_JVM_OPTS=-Xmx1024m -Dfile.encoding=UTF-8
 
 set DIRNAME=%~dp0
 if "%DIRNAME%" == "" set DIRNAME=.
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 3dc1a61..0f1f0f4 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
@@ -21,7 +21,6 @@ import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.DependencySet;
-import org.gradle.api.artifacts.ResolvableDependencies;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
@@ -57,12 +56,10 @@ public class AntlrPlugin implements Plugin<Project> {
                 .setVisible(false)
                 .setDescription("The Antlr libraries to be used for this project.");
 
-        antlrConfiguration.getIncoming().beforeResolve(new Action<ResolvableDependencies>() {
-            public void execute(ResolvableDependencies resolvableDependencies) {
-                DependencySet dependencies = antlrConfiguration.getDependencies();
-                if (dependencies.isEmpty()) {
-                    dependencies.add(project.getDependencies().create("antlr:antlr:2.7.7 at jar"));
-                }
+        antlrConfiguration.defaultDependencies(new Action<DependencySet>() {
+            @Override
+            public void execute(DependencySet dependencies) {
+                dependencies.add(project.getDependencies().create("antlr:antlr:2.7.7 at jar"));
             }
         });
 
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 88eca06..ea372f5 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
@@ -111,6 +111,11 @@ public class AntlrTask extends SourceTask {
         }
     }
 
+
+    /**
+     * List of command-line arguments passed to the antlr process
+     * @return The antlr command-line arguments
+     */
     @Input
     public List<String> getArguments() {
         return arguments;
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/ExpressionReplacingVisitorSupport.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/ExpressionReplacingVisitorSupport.java
new file mode 100644
index 0000000..5884824
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/ExpressionReplacingVisitorSupport.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.*;
+import org.codehaus.groovy.ast.stmt.*;
+import org.codehaus.groovy.classgen.BytecodeExpression;
+
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Groovy AST visitor that allows to replace both statements and expressions.
+ */
+public class ExpressionReplacingVisitorSupport extends StatementReplacingVisitorSupport {
+    private Expression replacementExpr;
+
+    public Expression replaceExpr(Expression expr) {
+        replacementExpr = null;
+        expr.visit(this);
+        Expression result = replacementExpr == null ? expr : replacementExpr;
+        replacementExpr = null;
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends Expression> void replaceAllExprs(List<T> exprs) {
+        ListIterator<T> iter = exprs.listIterator();
+        while (iter.hasNext()) {
+            iter.set((T) replaceExpr(iter.next()));
+        }
+    }
+
+    /*
+     * Replaces the currently visited expression with the specified expression.
+     */
+    protected void replaceVisitedExpressionWith(Expression other) {
+        replacementExpr = other;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitBlockStatement(BlockStatement stat) {
+        replaceAll(stat.getStatements());
+    }
+
+    @Override
+    public void visitForLoop(ForStatement stat) {
+        stat.setCollectionExpression(replaceExpr(stat.getCollectionExpression()));
+        stat.setLoopBlock(replace(stat.getLoopBlock()));
+    }
+
+    @Override
+    public void visitWhileLoop(WhileStatement stat) {
+        stat.setBooleanExpression((BooleanExpression) replaceExpr(stat.getBooleanExpression()));
+        stat.setLoopBlock(replace(stat.getLoopBlock()));
+    }
+
+    @Override
+    public void visitDoWhileLoop(DoWhileStatement stat) {
+        stat.setBooleanExpression((BooleanExpression) replaceExpr(stat.getBooleanExpression()));
+        stat.setLoopBlock(replace(stat.getLoopBlock()));
+    }
+
+    @Override
+    public void visitIfElse(IfStatement stat) {
+        stat.setBooleanExpression((BooleanExpression) replaceExpr(stat.getBooleanExpression()));
+        stat.setIfBlock(replace(stat.getIfBlock()));
+        stat.setElseBlock(replace(stat.getElseBlock()));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitTryCatchFinally(TryCatchStatement stat) {
+        stat.setTryStatement(replace(stat.getTryStatement()));
+        replaceAll(stat.getCatchStatements());
+        stat.setFinallyStatement(replace(stat.getFinallyStatement()));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitSwitch(SwitchStatement stat) {
+        stat.setExpression(replaceExpr(stat.getExpression()));
+        replaceAll(stat.getCaseStatements());
+        stat.setDefaultStatement(replace(stat.getDefaultStatement()));
+    }
+
+    @Override
+    public void visitCaseStatement(CaseStatement stat) {
+        stat.setExpression(replaceExpr(stat.getExpression()));
+        stat.setCode(replace(stat.getCode()));
+    }
+
+    @Override
+    public void visitSynchronizedStatement(SynchronizedStatement stat) {
+        stat.setExpression(replaceExpr(stat.getExpression()));
+        stat.setCode(replace(stat.getCode()));
+    }
+
+    @Override
+    public void visitCatchStatement(CatchStatement stat) {
+        stat.setCode(replace(stat.getCode()));
+    }
+
+    @Override
+    public void visitMethodCallExpression(MethodCallExpression expr) {
+        expr.setObjectExpression(replaceExpr(expr.getObjectExpression()));
+        expr.setMethod(replaceExpr(expr.getMethod()));
+        expr.setArguments(replaceExpr(expr.getArguments()));
+    }
+
+    @Override
+    public void visitStaticMethodCallExpression(StaticMethodCallExpression expr) {
+        StaticMethodCallExpression result = new StaticMethodCallExpression(
+            expr.getOwnerType(),
+            expr.getMethod(),
+            replaceExpr(expr.getArguments()));
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitConstructorCallExpression(ConstructorCallExpression expr) {
+        ConstructorCallExpression result = new ConstructorCallExpression(
+            expr.getType(),
+            replaceExpr(expr.getArguments()));
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitBinaryExpression(BinaryExpression expr) {
+        expr.setLeftExpression(replaceExpr(expr.getLeftExpression()));
+        expr.setRightExpression(replaceExpr(expr.getRightExpression()));
+    }
+
+    @Override
+    public void visitTernaryExpression(TernaryExpression expr) {
+        TernaryExpression result = new TernaryExpression(
+            (BooleanExpression) replaceExpr(expr.getBooleanExpression()),
+            replaceExpr(expr.getTrueExpression()),
+            replaceExpr(expr.getFalseExpression()));
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitShortTernaryExpression(ElvisOperatorExpression expr) {
+        ElvisOperatorExpression result = new ElvisOperatorExpression(
+            replaceExpr(expr.getTrueExpression()),
+            replaceExpr(expr.getFalseExpression()));
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitPostfixExpression(PostfixExpression expr) {
+        expr.setExpression(replaceExpr(expr.getExpression()));
+    }
+
+    @Override
+    public void visitPrefixExpression(PrefixExpression expr) {
+        expr.setExpression(replaceExpr(expr.getExpression()));
+    }
+
+    @Override
+    public void visitBooleanExpression(BooleanExpression expr) {
+        BooleanExpression result = new BooleanExpression(
+            replaceExpr(expr.getExpression()));
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitNotExpression(NotExpression expr) {
+        NotExpression result = new NotExpression(
+            replaceExpr(expr.getExpression()));
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitClosureExpression(ClosureExpression expr) {
+        super.visitClosureExpression(expr);
+        replaceVisitedExpressionWith(expr);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitTupleExpression(TupleExpression expr) {
+        replaceAllExprs(expr.getExpressions());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitListExpression(ListExpression expr) {
+        replaceAllExprs(expr.getExpressions());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitArrayExpression(ArrayExpression expr) {
+        replaceAllExprs(expr.getExpressions());
+        replaceAllExprs(expr.getSizeExpression());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitMapExpression(MapExpression expr) {
+        replaceAllExprs(expr.getMapEntryExpressions());
+
+    }
+
+    @Override
+    public void visitMapEntryExpression(MapEntryExpression expr) {
+        MapEntryExpression result = new MapEntryExpression(
+            replaceExpr(expr.getKeyExpression()),
+            replaceExpr(expr.getValueExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitRangeExpression(RangeExpression expr) {
+        RangeExpression result = new RangeExpression(
+            replaceExpr(expr.getFrom()),
+            replaceExpr(expr.getTo()),
+            expr.isInclusive()
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitSpreadExpression(SpreadExpression expr) {
+        SpreadExpression result = new SpreadExpression(
+            replaceExpr(expr.getExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitSpreadMapExpression(SpreadMapExpression expr) {
+        SpreadMapExpression result = new SpreadMapExpression(
+            replaceExpr(expr.getExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitMethodPointerExpression(MethodPointerExpression expr) {
+        MethodPointerExpression result = new MethodPointerExpression(
+            replaceExpr(expr.getExpression()),
+            expr.getMethodName()
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitUnaryMinusExpression(UnaryMinusExpression expr) {
+        UnaryMinusExpression result = new UnaryMinusExpression(
+            replaceExpr(expr.getExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitUnaryPlusExpression(UnaryPlusExpression expr) {
+        UnaryPlusExpression result = new UnaryPlusExpression(
+            replaceExpr(expr.getExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitBitwiseNegationExpression(BitwiseNegationExpression expr) {
+        BitwiseNegationExpression result = new BitwiseNegationExpression(
+            replaceExpr(expr.getExpression())
+        );
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitCastExpression(CastExpression expr) {
+        CastExpression result = new CastExpression(
+            expr.getType(),
+            replaceExpr(expr.getExpression()),
+            expr.isIgnoringAutoboxing()
+        );
+        result.setCoerce(expr.isCoerce());
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitDeclarationExpression(DeclarationExpression expr) {
+        visitBinaryExpression(expr);
+        replaceVisitedExpressionWith(expr);
+    }
+
+    @Override
+    public void visitPropertyExpression(PropertyExpression expr) {
+        PropertyExpression result = new PropertyExpression(
+            replaceExpr(expr.getObjectExpression()),
+            replaceExpr(expr.getProperty()),
+            expr.isSafe()
+        );
+        result.setSpreadSafe(expr.isSpreadSafe());
+        result.setStatic(expr.isStatic());
+        result.setImplicitThis(expr.isImplicitThis());
+        result.setType(expr.getType());
+        result.setSourcePosition(expr);
+        replaceVisitedExpressionWith(result);
+    }
+
+    @Override
+    public void visitAttributeExpression(AttributeExpression expr) {
+        visitPropertyExpression(expr);
+        replaceVisitedExpressionWith(expr);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void visitGStringExpression(GStringExpression expr) {
+        replaceAllExprs(expr.getStrings());
+        replaceAllExprs(expr.getValues());
+    }
+
+    @Override
+    public void visitArgumentlistExpression(ArgumentListExpression expr) {
+        visitTupleExpression(expr);
+        replaceVisitedExpressionWith(expr);
+    }
+
+    @Override
+    public void visitClosureListExpression(ClosureListExpression expr) {
+        visitListExpression(expr);
+        replaceVisitedExpressionWith(expr);
+    }
+
+    @Override
+    public void visitAssertStatement(AssertStatement stat) {
+        replaceExpr(stat.getBooleanExpression());
+        replaceExpr(stat.getMessageExpression());
+    }
+
+    @Override
+    public void visitExpressionStatement(ExpressionStatement stat) {
+        replaceExpr(stat.getExpression());
+    }
+
+    @Override
+    public void visitReturnStatement(ReturnStatement stat) {
+        replaceExpr(stat.getExpression());
+    }
+
+    @Override
+    public void visitThrowStatement(ThrowStatement stat) {
+        replaceExpr(stat.getExpression());
+    }
+
+    @Override
+    protected void visitListOfExpressions(List exprs) {
+        throw new UnsupportedOperationException("visitListOfExpressions");
+    }
+
+    // remaining methods are here to make sure we didn't forget anything
+
+    @Override
+    public void visitBreakStatement(BreakStatement stat) {
+    }
+
+    @Override
+    public void visitContinueStatement(ContinueStatement stat) {
+    }
+
+    @Override
+    public void visitConstantExpression(ConstantExpression expr) {
+    }
+
+    @Override
+    public void visitClassExpression(ClassExpression expr) {
+    }
+
+    @Override
+    public void visitVariableExpression(VariableExpression expr) {
+    }
+
+    @Override
+    public void visitFieldExpression(FieldExpression expr) {
+    }
+
+    @Override
+    public void visitBytecodeExpression(BytecodeExpression expr) {
+    }
+}
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/StatementReplacingVisitorSupport.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/StatementReplacingVisitorSupport.java
new file mode 100644
index 0000000..223cde0
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/groovy/scripts/internal/StatementReplacingVisitorSupport.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.List;
+import java.util.ListIterator;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.stmt.*;
+import org.codehaus.groovy.control.SourceUnit;
+
+/**
+ * Adds the ability to replace statements.
+ */
+// Implementation note: It is only necessary to override visit methods
+// for AST nodes that reference statements. For ClosureExpression we rely on
+// the assumption that it always references a BlockStatement and hence our
+// visitBlockStatement() method gets called.
+public abstract class StatementReplacingVisitorSupport extends ClassCodeVisitorSupport {
+  private Statement replacement;
+
+  /**
+   * Visits the specified statement. If the statement's visit method calls
+   * replaceVisitedMethodWith(), the statement will be replaced.
+   */
+  public Statement replace(Statement stat) {
+    replacement = null;
+    stat.visit(this);
+    Statement result = replacement == null ? stat : replacement;
+    replacement = null;
+    return result;
+  }
+
+  /**
+   * Visits the statements in the specified mutable list. If a statement's
+   * visit method calls replaceVisitedMethodWith(), the statement will be
+   * replaced.
+   */
+  @SuppressWarnings("unchecked")
+  protected <T extends Statement> void replaceAll(List<T> stats) {
+    ListIterator<T> iter = stats.listIterator();
+      while (iter.hasNext()) {
+          iter.set((T) replace(iter.next()));
+      }
+  }
+
+  /**
+   * Replaces the currently visited statement with the specified statement.
+   */
+  protected void replaceVisitedStatementWith(Statement other) {
+    replacement = other;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void visitBlockStatement(BlockStatement stat) {
+    replaceAll(stat.getStatements());
+  }
+
+  @Override
+  public void visitForLoop(ForStatement stat) {
+    stat.getCollectionExpression().visit(this);
+    stat.setLoopBlock(replace(stat.getLoopBlock()));
+  }
+
+  @Override
+  public void visitWhileLoop(WhileStatement stat) {
+    stat.getBooleanExpression().visit(this);
+    stat.setLoopBlock(replace(stat.getLoopBlock()));
+  }
+
+  @Override
+  public void visitDoWhileLoop(DoWhileStatement stat) {
+    stat.getBooleanExpression().visit(this);
+    stat.setLoopBlock(replace(stat.getLoopBlock()));
+  }
+
+  @Override
+  public void visitIfElse(IfStatement stat) {
+    stat.getBooleanExpression().visit(this);
+    stat.setIfBlock(replace(stat.getIfBlock()));
+    stat.setElseBlock(replace(stat.getElseBlock()));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void visitTryCatchFinally(TryCatchStatement stat) {
+    stat.setTryStatement(replace(stat.getTryStatement()));
+    replaceAll(stat.getCatchStatements());
+    stat.setFinallyStatement(replace(stat.getFinallyStatement()));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void visitSwitch(SwitchStatement stat) {
+    stat.getExpression().visit(this);
+    replaceAll(stat.getCaseStatements());
+    stat.setDefaultStatement(replace(stat.getDefaultStatement()));
+  }
+
+  @Override
+  public void visitCaseStatement(CaseStatement stat) {
+    stat.getExpression().visit(this);
+    stat.setCode(replace(stat.getCode()));
+  }
+
+  @Override
+  public void visitSynchronizedStatement(SynchronizedStatement stat) {
+    stat.getExpression().visit(this);
+    stat.setCode(replace(stat.getCode()));
+  }
+
+  @Override
+  public void visitCatchStatement(CatchStatement stat) {
+    stat.setCode(replace(stat.getCode()));
+  }
+
+  @Override
+  protected SourceUnit getSourceUnit() {
+    throw new UnsupportedOperationException("getSourceUnit");
+  }
+}
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
index e1d5704..50a4166 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java
@@ -22,6 +22,7 @@ import org.gradle.api.specs.Spec;
 
 import java.io.Serializable;
 import java.util.Arrays;
+import java.util.Collection;
 
 public abstract class Actions {
 
@@ -203,4 +204,18 @@ public abstract class Actions {
         }
     }
 
+    public static <T> T with(T instance, Action<? super T> action) {
+        action.execute(instance);
+        return instance;
+    }
+
+    public static <T> Action<T> add(final Collection<? super T> collection) {
+        return new Action<T>() {
+            @Override
+            public void execute(T t) {
+                collection.add(t);
+            }
+        };
+    }
+
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/BiActions.java b/subprojects/base-services/src/main/java/org/gradle/internal/BiActions.java
index b84a582..3d17a27 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/BiActions.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/BiActions.java
@@ -16,6 +16,8 @@
 
 package org.gradle.internal;
 
+import org.gradle.api.Action;
+
 public abstract class BiActions {
 
     private static final BiAction<Object, Object> NOOP = new BiAction<Object, Object>() {
@@ -31,4 +33,22 @@ public abstract class BiActions {
         return NOOP;
     }
 
+    public static <A, B> BiAction<A, B> composite(final BiAction<? super A, ? super B>... actions) {
+        return new BiAction<A, B>() {
+            @Override
+            public void execute(A a, B b) {
+                for (BiAction<? super A, ? super B> action : actions) {
+                    action.execute(a, b);
+                }
+            }
+        };
+    }
+
+    public static <A> BiAction<A, Object> usingFirstArgument(final Action<? super A> action) {
+        return new BiAction<A, Object>() {
+            public void execute(A a, Object o) {
+                action.execute(a);
+            }
+        };
+    }
 }
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
index 507d22d..1bb4cb7 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/FileUtils.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/FileUtils.java
@@ -16,16 +16,19 @@
 
 package org.gradle.internal;
 
+import com.google.common.collect.Lists;
 import org.gradle.api.GradleException;
 
 import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 
 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.
+     * 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();
@@ -47,11 +50,49 @@ public class FileUtils {
         return rc.toString();
     }
 
-    public static File assertInWindowsPathLengthLimitation(File file){
-        if(file.getAbsolutePath().length() > WINDOWS_PATH_LIMIT){
+    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;
     }
+
+    /**
+     * Returns the outer most files that encompass the given files inclusively.
+     * <p>
+     * This method does not access the file system.
+     * It is agnostic to whether a given file object represents a regular file, directory or does not exist.
+     * That is, the term “file” is used in the java.io.File sense, not the regular file sense.
+     *
+     * @param files the site of files to find the encompassing roots of
+     * @return the encompassing roots
+     */
+    public static Collection<? extends File> calculateRoots(Iterable<? extends File> files) {
+        List<File> roots = Lists.newLinkedList();
+
+        files:
+        for (File file : files) {
+            File absoluteFile = file.getAbsoluteFile();
+            String path = absoluteFile + File.separator;
+            Iterator<File> rootsIterator = roots.iterator();
+
+            while (rootsIterator.hasNext()) {
+                File root = rootsIterator.next();
+                String rootPath = root.getPath() + File.separator;
+                if (path.startsWith(rootPath)) { // is lower than root
+                    continue files;
+                }
+
+                if (rootPath.startsWith(path)) { // is higher than root
+                    rootsIterator.remove();
+                }
+            }
+
+            roots.add(absoluteFile);
+        }
+
+        return roots;
+    }
+
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Specs.java b/subprojects/base-services/src/main/java/org/gradle/internal/Specs.java
new file mode 100644
index 0000000..10f3612
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Specs.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.specs.Spec;
+
+public class Specs {
+    public static <T> Spec<T> isInstance(final Class<?> type) {
+        return new Spec<T>() {
+            public boolean isSatisfiedBy(T element) {
+                return type.isInstance(element);
+            }
+        };
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java b/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java
index 7ad53b4..592de9d 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java
@@ -56,4 +56,4 @@ public final class UncheckedException extends RuntimeException {
     public static RuntimeException unwrapAndRethrow(InvocationTargetException e) {
         return UncheckedException.throwAsUncheckedException(e.getTargetException());
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/classloader/ClassLoaderFactory.java
index fe2d1b0..b61174d 100644
--- 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
@@ -48,4 +48,6 @@ public interface ClassLoaderFactory {
      * Creates a ClassLoader from its spec.
      */
     ClassLoader createClassLoader(ClassLoaderSpec spec, List<? extends ClassLoader> parents);
+
+    FilteringClassLoader createSystemFilteringClassLoader();
 }
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
index 1a8177e..cc4c836 100644
--- 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
@@ -34,7 +34,7 @@ import java.util.List;
 public class DefaultClassLoaderFactory implements ClassLoaderFactory {
     @Override
     public ClassLoader getIsolatedSystemClassLoader() {
-        return ClassLoader.getSystemClassLoader().getParent();
+        return getSystemClassLoader().getParent();
     }
 
     public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
@@ -62,7 +62,7 @@ public class DefaultClassLoaderFactory implements ClassLoaderFactory {
 
         if (needJaxpImpl()) {
             try {
-                classpath.add(ClasspathUtil.getClasspathForResource(ClassLoader.getSystemClassLoader(), "META-INF/services/javax.xml.parsers.SAXParserFactory").toURI().toURL());
+                classpath.add(ClasspathUtil.getClasspathForResource(getSystemClassLoader(), "META-INF/services/javax.xml.parsers.SAXParserFactory").toURI().toURL());
             } catch (MalformedURLException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
@@ -75,7 +75,7 @@ public class DefaultClassLoaderFactory implements ClassLoaderFactory {
         // See the comment for {@link #createIsolatedClassLoader} above
         FilteringClassLoader classLoader = new FilteringClassLoader(parent);
         if (needJaxpImpl()) {
-            ServiceLocator locator = new ServiceLocator(ClassLoader.getSystemClassLoader());
+            ServiceLocator locator = new ServiceLocator(getSystemClassLoader());
             makeServiceVisible(locator, classLoader, SAXParserFactory.class);
             makeServiceVisible(locator, classLoader, DocumentBuilderFactory.class);
             makeServiceVisible(locator, classLoader, DatatypeFactory.class);
@@ -105,6 +105,11 @@ public class DefaultClassLoaderFactory implements ClassLoaderFactory {
         throw new IllegalArgumentException(String.format("Don't know how to create a ClassLoader from spec %s", spec));
     }
 
+    @Override
+    public FilteringClassLoader createSystemFilteringClassLoader() {
+        return createFilteringClassLoader(getSystemClassLoader());
+    }
+
     private void makeServiceVisible(ServiceLocator locator, FilteringClassLoader classLoader, Class<?> serviceType) {
         classLoader.allowClass(locator.getFactory(serviceType).getImplementationClass());
         classLoader.allowResource("META-INF/services/" + serviceType.getName());
@@ -113,4 +118,8 @@ public class DefaultClassLoaderFactory implements ClassLoaderFactory {
     private boolean needJaxpImpl() {
         return ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory") != null;
     }
+
+    private ClassLoader getSystemClassLoader() {
+        return ClassLoader.getSystemClassLoader();
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JdkTools.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JdkTools.java
index 985a3fb..6a29903 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JdkTools.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JdkTools.java
@@ -16,7 +16,10 @@
 
 package org.gradle.internal.jvm;
 
+import org.gradle.api.JavaVersion;
 import org.gradle.internal.classloader.DefaultClassLoaderFactory;
+import org.gradle.internal.classloader.FilteringClassLoader;
+import org.gradle.internal.classloader.MutableURLClassLoader;
 import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.reflect.DirectInstantiator;
 
@@ -45,12 +48,20 @@ public class JdkTools {
     }
 
     JdkTools(JavaInfo javaInfo) {
-        File toolsJar = javaInfo.getToolsJar();
-        if (toolsJar == null) {
-            throw new IllegalStateException("Could not find tools.jar");
+        DefaultClassLoaderFactory defaultClassLoaderFactory = new DefaultClassLoaderFactory();
+        JavaVersion javaVersion = Jvm.current().getJavaVersion();
+        FilteringClassLoader filteringClassLoader = defaultClassLoaderFactory.createSystemFilteringClassLoader();
+        if (!javaVersion.isJava9Compatible()) {
+            File toolsJar = javaInfo.getToolsJar();
+            if (toolsJar == null) {
+                throw new IllegalStateException("Could not find tools.jar");
+            }
+            DefaultClassPath defaultClassPath = new DefaultClassPath(toolsJar);
+            isolatedToolsLoader = new MutableURLClassLoader(filteringClassLoader, defaultClassPath.getAsURLs());
+        } else {
+            filteringClassLoader.allowPackage("com.sun.tools");
+            isolatedToolsLoader = filteringClassLoader;
         }
-
-        isolatedToolsLoader = new DefaultClassLoaderFactory().createIsolatedClassLoader(new DefaultClassPath(toolsJar));
     }
 
     public JavaCompiler getSystemJavaCompiler() {
@@ -58,10 +69,8 @@ public class JdkTools {
         try {
             compilerImplClass = isolatedToolsLoader.loadClass(DEFAULT_COMPILER_IMPL_NAME);
         } catch (ClassNotFoundException e) {
-            throw new IllegalStateException("Could not load class '" + DEFAULT_COMPILER_IMPL_NAME + "' from " + Jvm.current().getToolsJar());
+            throw new IllegalStateException("Could not load class '" + DEFAULT_COMPILER_IMPL_NAME);
         }
-
         return DirectInstantiator.instantiate(compilerImplClass.asSubclass(JavaCompiler.class));
     }
-
 }
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 18d68bd..f7c1ae4 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
@@ -31,9 +31,9 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
 public class Jvm implements JavaInfo {
-    
+
     private final static Logger LOGGER = LoggerFactory.getLogger(Jvm.class);
-    
+
     private final OperatingSystem os;
     //supplied java location
     private final File javaBase;
@@ -91,13 +91,12 @@ public class Jvm implements JavaInfo {
             this.javaVersion = null;
         }
     }
-    
+
     /**
      * Creates jvm instance for given java home. Attempts to validate if provided javaHome is a valid jdk or jre location.
      *
      * @param javaHome - location of your jdk or jre (jdk is safer), cannot be null
      * @return jvm for given java home
-     *
      * @throws org.gradle.internal.jvm.JavaHomeException when supplied javaHome does not seem to be a valid jdk or jre location
      * @throws IllegalArgumentException when supplied javaHome is not a valid folder
      */
@@ -128,19 +127,19 @@ public class Jvm implements JavaInfo {
 
         if (userSupplied) { //then we want to validate strictly
             throw new JavaHomeException(String.format("The supplied javaHome seems to be invalid."
-                    + " I cannot find the %s executable. Tried location: %s", command, executable.getAbsolutePath()));
+                + " I cannot find the %s executable. Tried location: %s", command, executable.getAbsolutePath()));
         }
 
         File pathExecutable = os.findInPath(command);
         if (pathExecutable != null) {
             LOGGER.info(String.format("Unable to find the '%s' executable using home: %s. We found it on the PATH: %s.",
-                    command, getJavaHome(), pathExecutable));
+                command, getJavaHome(), pathExecutable));
             return pathExecutable;
         }
 
         LOGGER.warn("Unable to find the '{}' executable. Tried the java home: {} and the PATH."
                 + " We will assume the executable can be ran in the current working folder.",
-                command, getJavaHome());
+            command, getJavaHome());
         return new File(os.getExecutableName(command));
     }
 
@@ -221,6 +220,9 @@ public class Jvm implements JavaInfo {
      */
     @Nullable
     public Jre getStandaloneJre() {
+        if (javaVersion.isJava9Compatible()) {
+            return null;
+        }
         if (os.isWindows()) {
             File jreDir;
             if (javaVersion.isJava5()) {
@@ -239,12 +241,15 @@ public class Jvm implements JavaInfo {
     }
 
     /**
-     * Locates the JRE installation for this JVM.
+     * Locates the JRE installation for this JVM. Returns null if no JRE installation is available.
      */
+    @Nullable
     public Jre getJre() {
         File jreDir = new File(javaBase, "jre");
         if (jreDir.isDirectory()) {
             return new DefaultJre(jreDir);
+        } else if (JavaVersion.current().isJava9Compatible()) {
+            return null;
         }
         return new DefaultJre(javaBase);
     }
@@ -299,8 +304,7 @@ public class Jvm implements JavaInfo {
     }
 
     /**
-     * Note: Implementation assumes that an Apple JVM always comes with a JDK rather than a JRE,
-     * but this is likely an over-simplification.
+     * Note: Implementation assumes that an Apple JVM always comes with a JDK rather than a JRE, but this is likely an over-simplification.
      */
     static class AppleJvm extends Jvm {
         AppleJvm(OperatingSystem os) {
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 dc41efd..7ded4c0 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
@@ -331,6 +331,14 @@ public class JavaReflectionUtil {
         return new InstantiatingFactory<T>(instantiator, type, args);
     }
 
+    public static boolean hasDefaultToString(Object object) {
+        try {
+            return object.getClass().getMethod("toString").getDeclaringClass() == Object.class;
+        } catch (java.lang.NoSuchMethodException e) {
+            throw new UncheckedException(e);
+        }
+    }
+
     private static class GetterMethodBackedPropertyAccessor<T, F> implements PropertyAccessor<T, F> {
         private final String property;
         private final Method method;
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 0a55df0..299774a 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
@@ -25,6 +25,7 @@ import org.gradle.api.Action;
 import org.gradle.api.Nullable;
 import org.gradle.api.Transformer;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.Cast;
 import org.gradle.internal.Factory;
 import org.gradle.internal.Pair;
 import org.gradle.internal.Transformers;
@@ -36,6 +37,13 @@ import static org.gradle.internal.Cast.cast;
 
 public abstract class CollectionUtils {
 
+    public static <T> Collection<? extends T> checkedCast(Class<T> type, Collection<?> input) {
+        for (Object o : input) {
+            cast(type, o);
+        }
+        return Cast.uncheckedCast(input);
+    }
+
     public static <T> T findFirst(Iterable<? extends T> source, Spec<? super T> filter) {
         for (T item : source) {
             if (filter.isSatisfiedBy(item)) {
@@ -343,12 +351,30 @@ public abstract class CollectionUtils {
         }
     }
 
+    /**
+     * Given a set of values, derive a set of keys and return a map
+     */
     public static <K, V> Map<K, V> collectMap(Iterable<? extends V> items, Transformer<? extends K, ? super V> keyGenerator) {
         Map<K, V> map = new LinkedHashMap<K, V>();
         collectMap(map, items, keyGenerator);
         return map;
     }
 
+    public static <K, V> void collectMapValues(Map<K, V> destination, Iterable<? extends K> keys, Transformer<? extends V, ? super K> keyGenerator) {
+        for (K item : keys) {
+            destination.put(item, keyGenerator.transform(item));
+        }
+    }
+
+    /**
+     * Given a set of keys, derive a set of values and return a map
+     */
+    public static <K, V> Map<K, V> collectMapValues(Iterable<? extends K> keys, Transformer<? extends V, ? super K> keyGenerator) {
+        Map<K, V> map = new LinkedHashMap<K, V>();
+        collectMapValues(map, keys, keyGenerator);
+        return map;
+    }
+
     public static <T> boolean every(Iterable<? extends T> things, Spec<? super T> predicate) {
         for (T thing : things) {
             if (!predicate.isSatisfiedBy(thing)) {
@@ -586,4 +612,4 @@ public abstract class CollectionUtils {
         }));
     }
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/BiActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/BiActionsTest.groovy
new file mode 100644
index 0000000..4a7c182
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/BiActionsTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 spock.lang.Specification
+
+class BiActionsTest extends Specification {
+
+    def "composite action executes all actions that are part of it"() {
+        given:
+        BiAction<List<String>, List<String>> first = {a, b -> a << "first a"; b << "first b" }
+        BiAction<List<String>, List<String>> second = {a, b -> a << "second a"; b << "second b" }
+        def composite = BiActions.composite(first, second)
+        def a = []
+        def b = []
+
+        when:
+        composite.execute(a, b)
+
+        then:
+        a == ["first a", "second a"]
+        b == ["first b", "second b"]
+    }
+
+    def "can wrap an action into a bi action that ignores second argument"() {
+        given:
+        Action<List<String>> action = {a -> a << "added by action" }
+        BiAction<List<String>, Object> biAction = BiActions.usingFirstArgument(action)
+        def argument = []
+
+        when:
+        biAction.execute(argument, null)
+
+        then:
+        argument == ["added by action"]
+    }
+}
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
index 3c445ab..cafee26 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/FileUtilsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/FileUtilsTest.groovy
@@ -20,11 +20,12 @@ import org.apache.commons.lang.RandomStringUtils
 import org.gradle.api.GradleException
 import spock.lang.Specification
 
-import static FileUtils.toSafeFileName
 import static FileUtils.assertInWindowsPathLengthLimitation
-
+import static FileUtils.toSafeFileName
+import static org.gradle.internal.FileUtils.calculateRoots
 
 class FileUtilsTest extends Specification {
+
     def "toSafeFileName encodes unsupported characters"() {
         expect:
         toSafeFileName(input) == output
@@ -38,7 +39,7 @@ class FileUtilsTest extends Specification {
         'with / \\ #' | 'with#20#2f#20#5c#20#23'
     }
 
-    def "assertInWindowsPathLengthLimitation throws exception when path limit exceeded"(){
+    def "assertInWindowsPathLengthLimitation throws exception when path limit exceeded"() {
         when:
         File inputFile = new File(RandomStringUtils.randomAlphanumeric(10))
         then:
@@ -51,4 +52,25 @@ class FileUtilsTest extends Specification {
         def e = thrown(GradleException);
         e.message.contains("exceeds windows path limitation of 260 character.")
     }
+
+    List<File> toRoots(Iterable<? extends File> files) {
+        calculateRoots(files)
+    }
+
+    List<File> files(String... paths) {
+        paths.collect { new File("/", it).absoluteFile }
+    }
+
+    def "can find roots when leafs are directories"() {
+        expect:
+        toRoots([]) == []
+        toRoots(files("a/a", "a/a")) == files("a/a")
+        toRoots(files("a", "b", "c")) == files("a", "b", "c")
+        toRoots(files("a/a", "a/a/a", "a/b/a")) == files("a/a", "a/b/a")
+        toRoots(files("a/a", "a/a/a", "b/a/a")) == files("a/a", "b/a/a")
+        toRoots(files("a/a/a/a/a/a/a/a/a", "a/b")) == files("a/a/a/a/a/a/a/a/a", "a/b")
+        toRoots(files("a/a/a/a/a/a/a/a/a", "a/b", "b/a/a/a/a/a/a/a/a/a/a/a")) == files("a/a/a/a/a/a/a/a/a", "a/b", "b/a/a/a/a/a/a/a/a/a/a/a")
+        toRoots(files("a/a/a/a/a/a/a/a/a", "a/b", "b/a/a/a/a/a/a/a/a/a/a/a", "b/a/a/a/a")) == files("a/a/a/a/a/a/a/a/a", "a/b", "b/a/a/a/a")
+    }
+
 }
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/SpecsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/SpecsTest.groovy
new file mode 100644
index 0000000..314ad2a
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/SpecsTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.specs.Spec
+import spock.lang.Specification
+
+class SpecsTest extends Specification {
+
+    static class A {}
+    static class B extends A {}
+    static class C extends B {}
+
+    def "isInstance spec is satisfied for all instances extending from the specified type"() {
+        when:
+        Spec<Object> spec = Specs.isInstance(B)
+
+        then:
+        !spec.isSatisfiedBy(new Object())
+        !spec.isSatisfiedBy(new A())
+        spec.isSatisfiedBy(new B())
+        spec.isSatisfiedBy(new C())
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JdkToolsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JdkToolsTest.groovy
index ab5609d..8982d1d 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JdkToolsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JdkToolsTest.groovy
@@ -51,5 +51,4 @@ class JdkToolsTest extends Specification {
         then:
         thrown IllegalStateException
     }
-
 }
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 8f9ca5b..4eac81e 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
@@ -24,8 +24,10 @@ import org.junit.Rule
 import spock.lang.Specification
 
 class JvmTest extends Specification {
-    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    @Rule
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    @Rule
+    SetSystemProperties sysProp = new SetSystemProperties()
     OperatingSystem os = Mock() {
         getExecutableName(_) >> { String name ->
             return "${name}.exe"
@@ -289,7 +291,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"() {
@@ -306,7 +308,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"() {
@@ -379,4 +381,43 @@ class JvmTest extends Specification {
         then:
         jvm.toString().contains('dummyFolder')
     }
+
+    def "locates MAC OS JDK9 install when java.home points to an EAP JDK 1.9 installation"() {
+        given:
+        OperatingSystem macOs = new OperatingSystem.MacOs()
+        TestFile software = tmpDir.createDir('software')
+        //http://openjdk.java.net/jeps/220
+        software.create {
+            Contents {
+                Home {
+                    bin {
+                        file 'java'
+                        file 'javac'
+                        file 'javadoc'
+                    }
+                    conf {
+                        'logging.properties'
+                    }
+                    lib {
+
+                    }
+                }
+            }
+        }
+
+        when:
+        System.properties['java.home'] = software.file('Contents/Home').absolutePath
+        System.properties['java.version'] = '1.9'
+        Jvm java9Vm = new Jvm(macOs)
+
+        then:
+        java9Vm.javaHome == software.file('Contents/Home')
+        java9Vm.javaExecutable == software.file('Contents/Home/bin/java')
+        java9Vm.javacExecutable == software.file('Contents/Home/bin/javac')
+        java9Vm.javadocExecutable == software.file('Contents/Home/bin/javadoc')
+        java9Vm.jre == null
+        java9Vm.runtimeJar == null
+        java9Vm.toolsJar == null
+        java9Vm.standaloneJre == null
+    }
 }
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 ba69424..b95efea 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
@@ -281,6 +281,18 @@ class JavaReflectionUtilTest extends Specification {
         !factory(instantiator, Thing).create().is(factory(instantiator, Thing).create())
     }
 
+    def "default toString methods"() {
+        expect:
+        hasDefaultToString(clazz)
+
+        where:
+        clazz << [new Object(), new Root()]
+    }
+
+    def "should not have a default toString"() {
+        expect:
+        !hasDefaultToString(new ClassWithToString())
+    }
 }
 
 @Retention(RetentionPolicy.RUNTIME)
@@ -321,4 +333,11 @@ class OverrideLast implements RootInterface, SubInterface, HasAnnotations {}
 
 class SuperWithInterface implements RootInterface {}
 
-class InheritsInterface extends SuperWithInterface {}
\ No newline at end of file
+class InheritsInterface extends SuperWithInterface {}
+
+class ClassWithToString {
+    @Override
+    public String toString() {
+        return "ClassWithToString{}";
+    }
+}
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 891354c..5987184 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
@@ -173,6 +173,12 @@ class CollectionUtilsTest extends Specification {
         collectMap([], transformer { it * 10 }) == [:]
     }
 
+    def "collect values as map"() {
+        expect:
+        collectMapValues([1, 2, 3], transformer { it * 10 }) == [1: 10, 2: 20, 3: 30]
+        collectMapValues([], transformer { it * 10 }) == [:]
+    }
+
     def "every"() {
         expect:
         every([1, 2, 3], spec { it < 4 })
@@ -372,4 +378,5 @@ class CollectionUtilsTest extends Specification {
     Action action(Closure c) {
         new ClosureBackedAction(c)
     }
-}
\ No newline at end of file
+
+}
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
index 8b42209..7575d50 100644
--- 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
@@ -276,7 +276,10 @@ it.exclude group: '*', module: 'badArtifact'
 
         when:
         libRequest(repo, "commons-lang", "commons-lang", 2.6)
+        // Required for the 'webinar-impl' project's POM
         libRequest(repo, "junit", "junit", 4.10)
+        // Required for the 'webinar-war' project's POM
+        libRequest(repo, "junit", "junit", "3.8.1")
         libRequest(repo, "org.hamcrest", "hamcrest-core", 1.1)
 
         run 'clean', 'build'
@@ -340,4 +343,4 @@ Root project 'webinar-parent'
         server.start()
         new MavenHttpRepository(server, '/maven', maven(file("maven_repo")));
     }
-}
\ No newline at end of file
+}
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
index b16a908..dacb94c 100644
--- 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
@@ -18,7 +18,7 @@
 
 package org.gradle.buildinit.plugins.internal.maven
 
-import org.gradle.mvn3.org.apache.maven.project.MavenProject
+import org.apache.maven.project.MavenProject
 import org.gradle.util.GFileUtils
 
 /**
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
index a304126..b2c9c0b 100644
--- 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
@@ -16,8 +16,8 @@
 
 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 org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.maven.project.MavenProject;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
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
index 842cbdf..7a85812 100644
--- 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
@@ -19,18 +19,18 @@ 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.apache.maven.execution.*;
+import org.apache.maven.project.*;
+import org.apache.maven.settings.Settings;
+import org.codehaus.plexus.ContainerConfiguration;
+import org.codehaus.plexus.DefaultContainerConfiguration;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.configuration.PlexusConfigurationException;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.util.DefaultRepositorySystemSession;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
@@ -53,9 +53,6 @@ public class MavenProjectsCreator {
     }
 
     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");
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
index c60107e..7bc9c03 100644
--- 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
@@ -16,11 +16,10 @@
 
 package org.gradle.buildinit.plugins.internal
 
-import org.gradle.api.file.FileTree
 import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.FileTreeInternal
 import spock.lang.Specification
 
-
 class LanguageLibraryProjectInitDescriptorSpec extends Specification {
 
     FileResolver fileResolver = Mock()
@@ -53,8 +52,8 @@ class LanguageLibraryProjectInitDescriptorSpec extends Specification {
 
     def "whenNoSourcesAvailable creates template operation checking for sources"(){
         setup:
-        def mainSourceDirectory = Mock(FileTree)
-        def testSourceDirectory = Mock(FileTree)
+        def mainSourceDirectory = Mock(FileTreeInternal)
+        def testSourceDirectory = Mock(FileTreeInternal)
         def delegate = Mock(TemplateOperation)
 
         descriptor = new LanguageLibraryProjectInitDescriptor("somelang", templateOperationFactory, fileResolver)
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 6924d3d..3daf583 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
@@ -47,10 +47,8 @@ class CheckstylePlugin extends AbstractCodeQualityPlugin<Checkstyle> {
     @Override
     protected void configureTaskDefaults(Checkstyle task, String baseName) {
         def conf = project.configurations['checkstyle']
-        conf.incoming.beforeResolve {
-            if (conf.dependencies.empty) {
-                conf.dependencies.add(project.dependencies.create("com.puppycrawl.tools:checkstyle:$extension.toolVersion"))
-            }
+        conf.defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("com.puppycrawl.tools:checkstyle:${this.extension.toolVersion}"))
         }
 
         task.conventionMapping.with {
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 59a2f6f..ef5114e 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
@@ -55,10 +55,8 @@ class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
     @Override
     protected void configureTaskDefaults(CodeNarc task, String baseName) {
         def codenarcConfiguration = project.configurations['codenarc']
-        codenarcConfiguration.incoming.beforeResolve {
-            if (codenarcConfiguration.dependencies.empty) {
-                codenarcConfiguration.dependencies.add(project.dependencies.create("org.codenarc:CodeNarc:$extension.toolVersion"))
-            }
+        codenarcConfiguration.defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("org.codenarc:CodeNarc:${this.extension.toolVersion}"))
         }
         task.conventionMapping.with {
             codenarcClasspath = { codenarcConfiguration }
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 f6eb0a2..29a8a47 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
@@ -73,10 +73,8 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
             pluginClasspath = project.configurations['findbugsPlugins']
         }
         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"))
-            }
+        config.defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("com.google.code.findbugs:findbugs:${this.extension.toolVersion}"))
         }
         task.conventionMapping.with {
             findbugsClasspath = { config }
@@ -88,7 +86,7 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
             excludeFilterConfig = { extension.excludeFilterConfig }
             includeFilterConfig = { extension.includeFilterConfig }
             excludeBugsFilterConfig = { extension.excludeBugsFilterConfig }
- 
+
         }
         task.reports.all { Report report ->
             report.conventionMapping.with {
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy
index a43fd30..ca39c7e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy
@@ -60,12 +60,10 @@ class JDependPlugin extends AbstractCodeQualityPlugin<JDepend> {
     @Override
     protected void configureTaskDefaults(JDepend task, String baseName) {
         def config = project.configurations['jdepend']
-        config.incoming.beforeResolve {
-            if (config.dependencies.empty) {
-                project.dependencies {
-                    jdepend "jdepend:jdepend:$extension.toolVersion"
-                    jdepend("org.apache.ant:ant-jdepend:1.9.4")
-                }
+        config.defaultDependencies { dependencies ->
+            this.project.dependencies {
+                jdepend "jdepend:jdepend:${this.extension.toolVersion}"
+                jdepend("org.apache.ant:ant-jdepend:1.9.4")
             }
         }
         task.conventionMapping.with {
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 0c84ea9..f2eebd9 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
@@ -76,12 +76,10 @@ class PmdPlugin extends AbstractCodeQualityPlugin<Pmd> {
     @Override
     protected void configureTaskDefaults(Pmd task, String baseName) {
         def config = project.configurations['pmd']
-        config.incoming.beforeResolve {
-            if (config.dependencies.empty) {
-                VersionNumber version = VersionNumber.parse(extension.toolVersion)
-                String dependency = calculateDefaultDependencyNotation(version)
-                config.dependencies.add(project.dependencies.create(dependency))
-            }
+        config.defaultDependencies { dependencies ->
+            VersionNumber version = VersionNumber.parse(this.extension.toolVersion)
+            String dependency = calculateDefaultDependencyNotation(version)
+            dependencies.add(this.project.dependencies.create(dependency))
         }
         task.conventionMapping.with {
             pmdClasspath = { config }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy
new file mode 100644
index 0000000..7039a02
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy
@@ -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
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class NativeServicesIntegrationTest extends AbstractIntegrationSpec {
+
+    def "native services libs are unpacked to gradle user home dir"() {
+        given:
+        def nativeDir = new File(executer.gradleUserHomeDir, "native")
+
+        when:
+        executer.withArguments("-q").run()
+
+        then:
+        nativeDir.directory
+    }
+
+}
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 3a47572..a218c0a 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
@@ -15,13 +15,15 @@
  */
 
 package org.gradle.api
-
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.ProjectLifecycleFixture
 import org.junit.Rule
+import org.junit.runner.RunWith
 import spock.lang.IgnoreIf
 
+ at RunWith(FluidDependenciesResolveRunner)
 class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule ProjectLifecycleFixture fixture = new ProjectLifecycleFixture(executer, temporaryFolder)
@@ -259,20 +261,24 @@ project(':api') {
             dependencies { compile project(":api") }
         """
         file("api/build.gradle") << "apply plugin: 'java'"
+        // Provide a source file so that the compile task doesn't skip resolving inputs
+        file("impl/src/main/java/Foo.java") << "public class Foo {}"
 
         when:
         run("impl:build")
 
         then:
+        executed ":api:jar", ":impl:jar"
         fixture.assertProjectsConfigured(":", ":impl", ":api")
 
         when:
         run("impl:build", "--no-rebuild") // impl -> api
 
         then:
-        //api tasks are not executed and api is not configured
-        !result.executedTasks.find { it.startsWith ":api" }
-        fixture.assertProjectsConfigured(":", ":impl")
+        executed ":impl:jar"
+        notExecuted ":api:jar"
+        // :api is configured to resolve impl.compile configuration
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
     }
 
     def "respects external task dependencies"() {
@@ -504,4 +510,4 @@ allprojects {
         result.assertTasksExecuted(":a:one")
         fixture.assertProjectsConfigured(":", ":b", ":b:child", ":a")
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptExecutionIntegrationTest.groovy
index 8db5110..6617f8c 100755
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptExecutionIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ExternalScriptExecutionIntegrationTest.groovy
@@ -24,6 +24,7 @@ import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.matchers.UserAgentMatcher
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 import org.junit.Test
 
@@ -36,6 +37,7 @@ public class ExternalScriptExecutionIntegrationTest extends AbstractIntegrationT
     public final HttpServer server = new HttpServer()
 
     @Test
+    @LeaksFileHandles
     public void executesExternalScriptAgainstAProjectWithCorrectEnvironment() {
         createExternalJar()
         createBuildSrc()
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptExecutionIntegrationTest.groovy
index ee98407..3c7ef94 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptExecutionIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/InitScriptExecutionIntegrationTest.groovy
@@ -19,6 +19,7 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.ArtifactBuilder
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
     def "executes init.gradle from user home dir"() {
@@ -55,6 +56,7 @@ class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
         b < c
     }
 
+    @LeaksFileHandles
     def "executes init script with correct environment"() {
         given:
         def implClassName = 'com.google.common.collect.Multimap'
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/PluginApplicationErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/PluginApplicationErrorIntegrationTest.groovy
index 80227a0..0b22d9c 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/PluginApplicationErrorIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/PluginApplicationErrorIntegrationTest.groovy
@@ -18,10 +18,12 @@ package org.gradle.api
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 class PluginApplicationErrorIntegrationTest extends AbstractIntegrationSpec {
     def pluginBuilder = new PluginBuilder(file("plugin"))
 
+    @LeaksFileHandles
     def "reports failure to apply plugin by id"() {
         given:
         pluginBuilder.addPlugin("throw new Exception('throwing plugin')", "broken")
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy
index 5245365..37645f0 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy
@@ -19,6 +19,7 @@ import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.executer.ArtifactBuilder
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Test
 
 import static org.hamcrest.Matchers.containsString
@@ -27,6 +28,7 @@ import static org.junit.Assert.assertThat
 
 class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationTest {
     @Test
+    @LeaksFileHandles
     public void executesSettingsScriptWithCorrectEnvironment() {
         createExternalJar()
         createBuildSrc()
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/PluginDetectionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/PluginDetectionIntegrationTest.groovy
index fd84bb7..1b80533 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/PluginDetectionIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/PluginDetectionIntegrationTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.dsl
 import org.gradle.api.Plugin
 import org.gradle.api.plugins.AppliedPlugin
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import spock.lang.Issue
 import spock.lang.Unroll
@@ -61,6 +62,7 @@ class PluginDetectionIntegrationTest extends AbstractIntegrationSpec {
         detectedBy << JAVA_PLUGIN_IDS + JAVA_PLUGIN_IDS.reverse()
     }
 
+    @LeaksFileHandles
     def "unqualified ids from classpath are detectable"() {
         def pluginBuilder = new PluginBuilder(testDirectory)
         pluginBuilder.addPlugin("")
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/initialization/loadercache/ClassLoadersCachingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/initialization/loadercache/ClassLoadersCachingIntegrationTest.groovy
index 1a1c966..bf48246 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/initialization/loadercache/ClassLoadersCachingIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/initialization/loadercache/ClassLoadersCachingIntegrationTest.groovy
@@ -16,20 +16,15 @@
 
 package org.gradle.api.internal.initialization.loadercache
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.PersistentBuildProcessIntegrationTest
 import org.gradle.integtests.fixtures.executer.ExecutionResult
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import spock.lang.Ignore
-import spock.lang.IgnoreIf
 
-//classloaders are cached in process so the test only makes sense if gradle invocations share the process
- at IgnoreIf({ !GradleContextualExecuter.longLivingProcess })
-class ClassLoadersCachingIntegrationTest extends AbstractIntegrationSpec {
+class ClassLoadersCachingIntegrationTest extends PersistentBuildProcessIntegrationTest {
 
     def cacheSizePerRun = []
 
     def setup() {
-        executer.requireIsolatedDaemons()
         file("cacheCheck.gradle") << """
             def cache = gradle.services.get(org.gradle.api.internal.initialization.loadercache.ClassLoaderCache)
             gradle.buildFinished {
@@ -556,4 +551,4 @@ class ClassLoadersCachingIntegrationTest extends AbstractIntegrationSpec {
         then:
         cached
     }
-}
\ No newline at end of file
+}
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 895465b..f16eb0a 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
@@ -22,6 +22,7 @@ import org.gradle.test.fixtures.archive.TarTestFixture
 import org.gradle.test.fixtures.file.TestFile
 import org.hamcrest.Matchers
 import org.junit.Rule
+import spock.lang.Issue
 
 import static org.hamcrest.Matchers.equalTo
 
@@ -117,6 +118,31 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         file('dest').assertHasDescendants('someDir/1.txt')
     }
 
+    @Issue("GRADLE-3310")
+    def "handles gzip compressed tars from resources.gzip"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tgzTo(file('test.tgz'))
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree(resources.gzip('test.tgz'))
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
     def "allows user to provide a custom resource for the tarTree"() {
         given:
         TestFile tar = file()
@@ -170,6 +196,31 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         file('dest').assertHasDescendants('someDir/1.txt')
     }
 
+    @Issue("GRADLE-3310")
+    def "handles bzip2 compressed tars from resources.bzip2"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tbzTo(file('test.tbz2'))
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree(resources.bzip2('test.tbz2'))
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
     def "knows compression of the tar"() {
         given:
         TestFile tar = file()
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
index 82eb139..f1f3651 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskRemovalIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskRemovalIntegrationTest.groovy
@@ -58,8 +58,6 @@ class TaskRemovalIntegrationTest extends AbstractIntegrationSpec {
     def "can remove task in after evaluate if task is used by unbound #annotationClass rule"() {
         given:
         buildScript """
-            import org.gradle.model.*
-
             task foo {}
 
             afterEvaluate {
@@ -89,8 +87,6 @@ class TaskRemovalIntegrationTest extends AbstractIntegrationSpec {
     def "cant remove task if used by rule"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             task foo {}
             task bar { doLast { tasks.remove(foo) } }
 
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleBasedTaskExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleBasedTaskExecutionIntegrationTest.groovy
index 166e478..d24071d 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleBasedTaskExecutionIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleBasedTaskExecutionIntegrationTest.groovy
@@ -105,8 +105,6 @@ class RuleBasedTaskExecutionIntegrationTest extends AbstractIntegrationSpec {
     def "tasks added via task container and not explicitly required but executed are self closed"() {
         given:
         buildScript '''
-            import org.gradle.model.collection.*
-
             class EchoTask extends DefaultTask {
                 String text = "default"
 
@@ -128,7 +126,7 @@ class RuleBasedTaskExecutionIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("requested") {
                         dependsOn "dependency"
                         finalizedBy "finalizer"
@@ -154,8 +152,6 @@ class RuleBasedTaskExecutionIntegrationTest extends AbstractIntegrationSpec {
         settingsFile << "include 'a', 'b'"
 
         buildScript """
-            import org.gradle.model.collection.*
-
             project(':a') {
                 apply type: ProjectARules
             }
@@ -166,7 +162,7 @@ class RuleBasedTaskExecutionIntegrationTest extends AbstractIntegrationSpec {
 
             class ProjectARules extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("executed") {
                         dependsOn ":b:dependency"
                     }
@@ -175,7 +171,7 @@ class RuleBasedTaskExecutionIntegrationTest extends AbstractIntegrationSpec {
 
             class ProjectBRules extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("dependency")
                 }
             }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy
index 9a1ebb2..b376e08 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/plugin/ScriptPluginClassLoadingIntegrationTest.groovy
@@ -18,10 +18,12 @@ package org.gradle.plugin
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Issue
 
 import static org.gradle.util.Matchers.containsText
 
+ at LeaksFileHandles
 class ScriptPluginClassLoadingIntegrationTest extends AbstractIntegrationSpec {
 
     def pluginBuilder = new PluginBuilder(file("plugin"))
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
index e3c1401..2a48b15 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -165,7 +165,7 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
                     prefix.append("  ");
                     details.details.style(Info).text("> ").style(Normal);
 
-                    return new LinePrefixingStyledTextOutput(details.details, prefix);
+                    return new LinePrefixingStyledTextOutput(details.details, prefix, false);
                 }
             });
         } else {
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index 207243a..9e07c34 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -75,6 +75,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private boolean parallelProjectExecution;
     private boolean configureOnDemand;
     private int maxWorkerCount;
+    private boolean continuous;
 
     /**
      * Sets the project's cache location. Set to null to use the default location.
@@ -691,4 +692,14 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     public void setConfigureOnDemand(boolean configureOnDemand) {
         this.configureOnDemand = configureOnDemand;
     }
-}
\ No newline at end of file
+
+    @Incubating
+    public boolean isContinuous() {
+        return continuous;
+    }
+
+    @Incubating
+    public void setContinuous(boolean enabled) {
+        this.continuous = enabled;
+    }
+}
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 39ebe9c..5f304be 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
@@ -16,6 +16,7 @@
 package org.gradle.api;
 
 import groovy.lang.Closure;
+import org.gradle.api.internal.rules.NamedDomainObjectFactoryRegistry;
 
 /**
  * A {@link org.gradle.api.PolymorphicDomainObjectContainer} that can be extended at runtime to
@@ -24,7 +25,7 @@ import groovy.lang.Closure;
  * @param <T> the (base) container element type
  */
 @Incubating
-public interface ExtensiblePolymorphicDomainObjectContainer<T> extends PolymorphicDomainObjectContainer<T> {
+public interface ExtensiblePolymorphicDomainObjectContainer<T> extends PolymorphicDomainObjectContainer<T>, NamedDomainObjectFactoryRegistry<T> {
     /**
      * Registers a factory for creating elements of the specified type. Typically, the specified type
      * is an interface type.
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 2243744..232048f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -1354,6 +1354,32 @@ public interface Project extends Comparable<Project>, ExtensionAware, PluginAwar
     CopySpec copySpec(Closure closure);
 
     /**
+     * Copies the specified files.  The given action is used to configure a {@link CopySpec}, which is then used to
+     * copy the files.
+     * @see #copy(Closure)
+     * @param action Action to configure the CopySpec
+     * @return {@link WorkResult} that can be used to check if the copy did any work.
+     */
+    WorkResult copy(Action<? super CopySpec> action);
+
+    /**
+     * Creates a {@link CopySpec} which can later be used to copy files or create an archive. The given action is used
+     * to configure the {@link CopySpec} before it is returned by this method.
+     *
+     * @see #copySpec(Closure)
+     * @param action Action to configure the CopySpec
+     * @return The CopySpec
+     */
+    CopySpec copySpec(Action<? super CopySpec> action);
+
+    /**
+     * Creates a {@link CopySpec} which can later be used to copy files or create an archive.
+     *
+     * @return a newly created copy spec
+     */
+    CopySpec copySpec();
+
+    /**
      * Returns the evaluation state of this project. You can use this to access information about the evaluation of this
      * project, such as whether it has failed.
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentSelectionRules.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentSelectionRules.java
index 076946e..16ad791 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentSelectionRules.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ComponentSelectionRules.java
@@ -65,7 +65,7 @@ public interface ComponentSelectionRules {
      * @param selectionAction the Action that implements a rule to be applied
      * @return this
      */
-    public ComponentSelectionRules all(Action<? super ComponentSelection> selectionAction);
+    ComponentSelectionRules all(Action<? super ComponentSelection> selectionAction);
 
     /**
      * Adds a component selection rule that will apply to all resolved components.
@@ -79,7 +79,7 @@ public interface ComponentSelectionRules {
      * @param closure the Closure that implements a rule to be applied
      * @return this
      */
-    public ComponentSelectionRules all(Closure<?> closure);
+    ComponentSelectionRules all(Closure<?> closure);
 
     /**
      * Adds a rule-source backed component selection rule that will apply to all resolved components.
@@ -96,7 +96,7 @@ public interface ComponentSelectionRules {
      * @param ruleSource an instance providing a rule implementation
      * @return this
      */
-    public ComponentSelectionRules all(Object ruleSource);
+    ComponentSelectionRules all(Object ruleSource);
 
     /**
      * Adds a component selection rule that will apply to the specified module.
@@ -106,7 +106,7 @@ public interface ComponentSelectionRules {
      * @param selectionAction the Action that implements a rule to be applied
      * @return this
      */
-    public ComponentSelectionRules withModule(Object id, Action<? super ComponentSelection> selectionAction);
+    ComponentSelectionRules withModule(Object id, Action<? super ComponentSelection> selectionAction);
 
     /**
      * Adds a component selection rule that will apply to the specified module.
@@ -121,7 +121,7 @@ public interface ComponentSelectionRules {
      * @param closure the Closure that implements a rule to be applied
      * @return this
      */
-    public ComponentSelectionRules withModule(Object id, Closure<?> closure);
+    ComponentSelectionRules withModule(Object id, Closure<?> closure);
 
     /**
      * Adds a rule-source backed component selection rule that will apply to the specified module.
@@ -139,5 +139,5 @@ public interface ComponentSelectionRules {
      * @param ruleSource an instance providing a rule implementation
      * @return this
      */
-    public ComponentSelectionRules withModule(Object id, Object ruleSource);
+    ComponentSelectionRules withModule(Object id, Object ruleSource);
 }
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 1aba04c..eca73b6 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
@@ -16,6 +16,8 @@
 package org.gradle.api.artifacts;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
@@ -89,7 +91,7 @@ public interface Configuration extends FileCollection {
             return c.getName();
         }
     }
-    
+
     /**
      * Returns true if this is a visible configuration. A visible configuration is usable outside the project it belongs
      * to. The default value is true.
@@ -294,14 +296,14 @@ public interface Configuration extends FileCollection {
 
     /**
      * Returns the artifacts of this configuration excluding the artifacts of extended configurations.
-     * 
+     *
      * @return The set.
      */
     PublishArtifactSet getArtifacts();
 
     /**
      * Returns the artifacts of this configuration including the artifacts of extended configurations.
-     * 
+     *
      * @return The (read-only) set.
      */
     PublishArtifactSet getAllArtifacts();
@@ -324,6 +326,36 @@ public interface Configuration extends FileCollection {
     Configuration exclude(Map<String, String> excludeProperties);
 
     /**
+     * Execute the given action if the configuration has no defined dependencies when it first participates in
+     * dependency resolution. A {@code Configuration} will participate in dependency resolution
+     * when:
+     * <ul>
+     *     <li>The {@link Configuration} itself is resolved</li>
+     *     <li>Another {@link Configuration} that extends this one is resolved</li>
+     *     <li>Another {@link Configuration} that references this one as a project dependency is resolved</li>
+     * </ul>
+     *
+     *
+     * This method is useful for specifying default dependencies for a configuration:
+     * <pre autoTested='true'>
+     * configurations { conf }
+     * configurations['conf'].defaultDependencies { dependencies ->
+     *      dependencies.add(owner.project.dependencies.create("org.gradle:my-util:1.0"))
+     * }
+     * </pre>
+     *
+     * A {@code Configuration} is considered empty even if it extends another, non-empty {@code Configuration}.
+     *
+     * If multiple actions are supplied, each action will be executed until the set of dependencies is no longer empty.
+     * Remaining actions will be ignored.
+     *
+     * @param action the action to execute when the configuration has no defined dependencies.
+     * @return this
+     */
+    @Incubating
+    Configuration defaultDependencies(Action<? super DependencySet> action);
+
+    /**
      * Returns all the configurations belonging to the same configuration container as this
      * configuration (including this configuration).
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitution.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitution.java
index 80849a8..04c058d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitution.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitution.java
@@ -23,30 +23,32 @@ import org.gradle.internal.HasInternalProtocol;
 /**
  * Provides means to substitute a different dependency during resolution.
  *
- * @param <T> the type of the requested component.
- *
- * @since 2.4
+ * @since 2.5
  */
 @Incubating
 @HasInternalProtocol
-public interface DependencySubstitution<T extends ComponentSelector> {
+public interface DependencySubstitution {
     /**
-     * The dependency, before it is resolved.
+     * The requested dependency, before it is resolved.
      * The requested dependency does not change even if there are multiple dependency substitution rules
      * that manipulate the dependency metadata.
      */
-    T getRequested();
+    ComponentSelector getRequested();
 
     /**
      * This method can be used to replace a dependency before it is resolved,
      * e.g. change group, name or version (or all three of them), or replace it
      * with a project dependency.
      *
+     * Accepted notations are:
+     * <ul>
+     *     <li>Strings encoding group:module:version, like 'org.gradle:gradle-core:2.4'</li>
+     *     <li>Maps like [group: 'org.gradle', name: 'gradle-core', version: '2.4']</li>
+     *     <li>Project instances like <code>project(":api")</code></li>
+     *     <li>Any instance of <code>ModuleComponentSelector</code> or <code>ProjectComponentSelector</code></li>
+     * </ul>
+     *
      * @param notation the notation that gets parsed into an instance of {@link ComponentSelector}.
-     * You can pass Strings like 'org.gradle:gradle-core:2.4',
-     * Maps like [group: 'org.gradle', name: 'gradle-core', version: '2.4'],
-     * Projects like <code>project(":api")</code>,
-     * or instances of <code>ModuleComponentSelector</code>.
      */
     void useTarget(Object notation);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitutions.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitutions.java
index 57cc54b..3c9973e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitutions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySubstitutions.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.artifacts;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.component.ComponentSelector;
@@ -24,144 +23,80 @@ import org.gradle.internal.HasInternalProtocol;
 
 /**
  * Allows replacing dependencies with other dependencies.
- *
- * <pre>
- * // add dependency substitution rules
- * dependencySubstitution {
- *   //specifying a fixed version for all libraries with 'org.gradle' group
- *   eachModule { ModuleDependencySubstitution details ->
- *     if (details.requested.group == 'org.gradle') {
- *       details.useVersion '2.4'
- *     }
- *     //changing 'groovy-all' into 'groovy':
- *     if (details.requested.name == 'groovy-all') {
- *       details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
- *     }
- *   }
- * }
- * </pre>
+ * @since 2.5
  */
 @HasInternalProtocol
 @Incubating
 public interface DependencySubstitutions {
     /**
      * Adds a dependency substitution rule that is triggered for every dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link DependencySubstitution<ComponentSelector>}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
-     */
-    // TODO:PREZI Perhaps we should call this eachDependency(), as we do it for example in ResolutionRules?
-    DependencySubstitutions all(Action<? super DependencySubstitution<? super ComponentSelector>> rule);
-
-    /**
-     * Adds a dependency substitution rule that is triggered for every dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link DependencySubstitution<ComponentSelector>}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
-     */
-    DependencySubstitutions all(Closure<?> rule);
-
-    /**
-     * Adds a dependency substitution rule that is triggered for every module dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ModuleDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
-     */
-    DependencySubstitutions eachModule(Action<? super ModuleDependencySubstitution> rule);
-
-    /**
-     * Adds a dependency substitution rule that is triggered for every module dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ModuleDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
-     */
-    DependencySubstitutions eachModule(Closure<?> rule);
-
-    /**
-     * Adds a dependency substitution rule that is triggered for a given module dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ModuleDependencySubstitution}
+     * when the configuration is being resolved. The action receives an instance of {@link DependencySubstitution}
      * that can be used to find out what dependency is being resolved and to influence the resolution process.
+     * <p/>
+     * Example:
+     * <pre autoTested=''>
+     * configurations { main }
+     * // add dependency substitution rules
+     * configurations.main.resolutionStrategy.dependencySubstitution {
+     *   // Use a rule to change the dependency module while leaving group + version intact
+     *   all { DependencySubstitution dependency ->
+     *     if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.name == 'groovy-all') {
+     *       dependency.useTarget details.requested.group + ':groovy:' + details.requested.version
+     *     }
+     *   }
+     *   // Use a rule to replace all missing projects with module dependencies
+     *   all { DependencySubstitution dependency ->
+     *    if (dependency.requested instanceof ProjectComponentSelector) {
+     *       def targetProject = findProject(":${dependency.requested.path}")
+     *       if (targetProject == null) {
+     *         dependency.useTarget "org.myorg:" + dependency.requested.path + ":+"
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
      *
      * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
      *
      * @return this
-     * @since 2.4
      */
-    DependencySubstitutions withModule(Object id, Action<? super ModuleDependencySubstitution> rule);
+    DependencySubstitutions all(Action<? super DependencySubstitution> rule);
 
     /**
-     * Adds a dependency substitution rule that is triggered for a given module dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ModuleDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
+     * Create a ModuleComponentSelector from the provided input string. Strings must be in the format "{group}:{module}:{version}".
      */
-    DependencySubstitutions withModule(Object id, Closure<?> rule);
+    ComponentSelector module(String notation);
 
     /**
-     * Adds a dependency substitution rule that is triggered for every project dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ProjectDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
+     * Create a ProjectComponentSelector from the provided input string. Strings must be in the format ":path".
      */
-    DependencySubstitutions eachProject(Action<? super ProjectDependencySubstitution> rule);
+    ComponentSelector project(String path);
 
     /**
-     * Adds a dependency substitution rule that is triggered for every project dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ProjectDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
+     * DSL-friendly mechanism to construct a dependency substitution for dependencies matching the provided selector.
+     * <p/>
+     * Examples:
+     * <pre autoTested=''>
+     * configurations { main }
+     * configurations.main.resolutionStrategy.dependencySubstitution {
+     *   // Substitute project and module dependencies
+     *   substitute module('org.gradle:api') with project(':api')
+     *   substitute project(':util') with module('org.gradle:util:3.0')
+     *
+     *   // Substitute one module dependency for another
+     *   substitute module('org.gradle:api:2.0') with module('org.gradle:api:2.1')
+     * }
+     * </pre>
      */
-    DependencySubstitutions eachProject(Closure<?> rule);
+    Substitution substitute(ComponentSelector substitutedDependency);
 
     /**
-     * Adds a dependency substitution rule that is triggered for a given project dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ProjectDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
-     */
-    DependencySubstitutions withProject(Object id, Action<? super ProjectDependencySubstitution> rule);
-
-    /**
-     * Adds a dependency substitution rule that is triggered for a given project dependency (including transitive)
-     * when the configuration is being resolved. The action receives an instance of {@link ProjectDependencySubstitution}
-     * that can be used to find out what dependency is being resolved and to influence the resolution process.
-     *
-     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link ResolutionStrategy#force(Object...)}
-     *
-     * @return this
-     * @since 2.4
+     * Provides a DSL-friendly mechanism for specifying the target of a substitution.
      */
-    DependencySubstitutions withProject(Object id, Closure<?> rule);
+    interface Substitution {
+        /**
+         * Specify the target of the substitution.
+         */
+        void with(ComponentSelector notation);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java
index 8d63e87..be91469 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java
@@ -15,9 +15,14 @@
  */
 package org.gradle.api.artifacts;
 
+import org.gradle.api.internal.file.FileSystemSubset;
+
 /**
  * A {@code FileCollectionDependency} is a {@link Dependency} on a collection of local files which are not stored in a
  * repository.
  */
 public interface FileCollectionDependency extends SelfResolvingDependency {
+
+    void registerWatchPoints(FileSystemSubset.Builder builder);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependencySubstitution.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependencySubstitution.java
deleted file mode 100644
index 0283ca7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependencySubstitution.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.artifacts.component.ModuleComponentSelector;
-import org.gradle.internal.HasInternalProtocol;
-
-/**
- * Provides means to substitute a different dependency in place of a module dependency.
- *
- * @since 2.4
- */
- at Incubating
- at HasInternalProtocol
-public interface ModuleDependencySubstitution extends DependencySubstitution<ModuleComponentSelector> {
-    /**
-     * Allows to override the version when the dependency {@link #getRequested()} is resolved.
-     * Can be used to select a version that is different than requested.
-     * Forcing modules via {@link ResolutionStrategy#force(Object...)} uses this capability.
-     * <p>
-     * If you need to change not only the version but also group or name please use the {@link #useTarget(Object)} method.
-     *
-     * @param version to use when resolving this dependency, cannot be null.
-     * It is valid to configure the same version as requested.
-     */
-    void useVersion(String version);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependencySubstitution.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependencySubstitution.java
deleted file mode 100644
index 1429ddf..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ProjectDependencySubstitution.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.artifacts.component.ProjectComponentSelector;
-import org.gradle.internal.HasInternalProtocol;
-
-/**
- * Provides means to substitute a different dependency in place of a project dependency.
- *
- * @since 2.4
- */
- at Incubating
- at HasInternalProtocol
-public interface ProjectDependencySubstitution extends DependencySubstitution<ProjectComponentSelector> {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
index dabf64e..1ee33f1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
@@ -44,16 +44,8 @@ import java.util.concurrent.TimeUnit;
  *
  *     // add dependency substitution rules
  *     dependencySubstitution {
- *       //specifying a fixed version for all libraries with 'org.gradle' group
- *       eachModule { ModuleDependencySubstitution details ->
- *         if (details.requested.group == 'org.gradle') {
- *           details.useVersion '2.4'
- *         }
- *         //changing 'groovy-all' into 'groovy':
- *         if (details.requested.name == 'groovy-all') {
- *           details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
- *         }
- *       }
+ *       substitute module('org.gradle:api') with project(':api')
+ *       substitute project(':util') with module('org.gradle:util:3.0')
  *     }
  *
  *     // cache dynamic versions for 10 minutes
@@ -147,10 +139,8 @@ public interface ResolutionStrategy {
      * that can be used to find out what dependency is being resolved and to influence the resolution process.
      * Example:
      * <pre autoTested=''>
-     * apply plugin: 'java' //so that there are some configurations
-     *
-     * configurations.all {
-     *   resolutionStrategy {
+     * configurations {
+     *   compile.resolutionStrategy {
      *     eachDependency { DependencyResolveDetails details ->
      *       //specifying a fixed version for all libraries with 'org.gradle' group
      *       if (details.requested.group == 'org.gradle') {
@@ -240,12 +230,38 @@ public interface ResolutionStrategy {
      */
     @Incubating
     ResolutionStrategy componentSelection(Action<? super ComponentSelectionRules> action);
-/*
 
+    /**
+     * Returns the set of dependency substitution rules that are set for this configuration.
+     *
+     * @since 2.5
+     */
     @Incubating
     DependencySubstitutions getDependencySubstitution();
 
+    /**
+     * Configures the set of dependency substitution rules for this configuration.  The action receives an instance of {@link DependencySubstitutions} which
+     * can then be configured with substitution rules.
+     * <p/>
+     * Examples:
+     * <pre autoTested=''>
+     * // add dependency substitution rules
+     * configurations.all {
+     *   resolutionStrategy.dependencySubstitution {
+     *     // Substitute project and module dependencies
+     *     substitute module('org.gradle:api') with project(':api')
+     *     substitute project(':util') with module('org.gradle:util:3.0')
+     *
+     *     // Substitute one module dependency for another
+     *     substitute module('org.gradle:api:2.0') with module('org.gradle:api:2.1')
+     *   }
+     * }
+     * </pre>
+     *
+     * @return this ResolutionStrategy instance
+     * @see DependencySubstitutions
+     * @since 2.5
+     */
     @Incubating
     ResolutionStrategy dependencySubstitution(Action<? super DependencySubstitutions> action);
-*/
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveContext.java
new file mode 100644
index 0000000..bbd9f04
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveContext.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.artifacts;
+
+/**
+ * Represents something that can be resolved.
+ */
+public interface ResolveContext {
+    String getName();
+
+    DependencySet getDependencies();
+
+    DependencySet getAllDependencies();
+
+}
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 8ffb3b5..1412a36 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,23 +16,23 @@
 
 package org.gradle.api.artifacts;
 
-import org.gradle.internal.exceptions.DefaultMultiCauseException;
 import org.gradle.internal.exceptions.Contextual;
+import org.gradle.internal.exceptions.DefaultMultiCauseException;
 
 /**
  * <p>A <code>ResolveException</code> is thrown when a dependency configuration cannot be resolved for some reason.</p>
  */
 @Contextual
 public class ResolveException extends DefaultMultiCauseException {
-    public ResolveException(Configuration configuration, Throwable cause) {
+    public ResolveException(ResolveContext configuration, Throwable cause) {
         super(buildMessage(configuration), cause);
     }
 
-    public ResolveException(Configuration configuration, Iterable<? extends Throwable> causes) {
+    public ResolveException(ResolveContext configuration, Iterable<? extends Throwable> causes) {
         super(buildMessage(configuration), causes);
     }
 
-    private static String buildMessage(Configuration configuration) {
+    private static String buildMessage(ResolveContext configuration) {
         return String.format("Could not resolve all dependencies for %s.", configuration);
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentIdentifier.java
new file mode 100644
index 0000000..ffb900e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentIdentifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 library instance that is built as part of the current build.
+ *
+ */
+ at Incubating
+public interface LibraryComponentIdentifier extends ComponentIdentifier {
+    String getProjectPath();
+    String getLibraryName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentSelector.java
new file mode 100644
index 0000000..59d51ce
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/component/LibraryComponentSelector.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 library instance that is built as part of the current build.
+ *
+ */
+ at Incubating
+public interface LibraryComponentSelector extends ComponentSelector {
+    String getProjectPath();
+    String getLibraryName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/InternalTaskExecutionListener.java b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/InternalTaskExecutionListener.java
new file mode 100644
index 0000000..520d999
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/InternalTaskExecutionListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.execution.internal;
+
+import org.gradle.internal.progress.OperationResult;
+import org.gradle.internal.progress.OperationStartEvent;
+
+public interface InternalTaskExecutionListener {
+
+    void beforeExecute(TaskOperationInternal taskOperation, OperationStartEvent startEvent);
+
+    void afterExecute(TaskOperationInternal taskOperation, OperationResult result);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskInputsListener.java b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskInputsListener.java
new file mode 100644
index 0000000..f414eba
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskInputsListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.execution.internal;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.file.FileCollectionInternal;
+
+public interface TaskInputsListener {
+
+    TaskInputsListener NOOP = new TaskInputsListener() {
+        @Override
+        public void onExecute(TaskInternal taskInternal, FileCollectionInternal fileSystemInputs) {
+
+        }
+    };
+
+    /**
+     * Called when the execution of the given task is imminent, or would have been if the given file collection was not currently empty.
+     * <p>
+     * The given files may not == taskInternal.inputs.files, as only a subset of that collection may be relevant to the task execution.
+     *
+     * @param taskInternal the task to be executed
+     * @param fileSystemInputs the file system inputs relevant to the task execution
+     */
+    void onExecute(TaskInternal taskInternal, FileCollectionInternal fileSystemInputs);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskOperationInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskOperationInternal.java
new file mode 100644
index 0000000..f93918c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/execution/internal/TaskOperationInternal.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.execution.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.TaskInternal;
+
+public final class TaskOperationInternal {
+    private final Object id;
+    private final Object parentId;
+    private final TaskInternal task;
+
+    public TaskOperationInternal(Object id, Object parentId, TaskInternal task) {
+        this.id = id;
+        this.parentId = parentId;
+        this.task = task;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    @Nullable
+    public Object getParentId() {
+        return parentId;
+    }
+
+    public TaskInternal getTask() {
+        return task;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCollection.java
index 8d86ca0..c9dddb5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCollection.java
@@ -20,6 +20,7 @@ import org.gradle.api.Buildable;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.AntBuilderAware;
 import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.internal.HasInternalProtocol;
 
 import java.io.File;
 import java.util.Set;
@@ -30,6 +31,7 @@ import java.util.Set;
  *
  * <p>You can obtain a {@code FileCollection} instance using {@link org.gradle.api.Project#files}.</p>
  */
+ at HasInternalProtocol
 public interface FileCollection extends Iterable<File>, AntBuilderAware, Buildable {
     /**
      * Returns the content of this collection, asserting it contains exactly one file.
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 b9b1764..839aa3d 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
@@ -17,6 +17,7 @@ package org.gradle.api.file;
 
 import groovy.lang.Closure;
 import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.internal.HasInternalProtocol;
 
 /**
  * <p>A {@code FileTree} represents a hierarchy of files. It extends {@link FileCollection} to add hierarchy query and
@@ -27,6 +28,7 @@ import org.gradle.api.tasks.util.PatternFilterable;
  * {@link org.gradle.api.Project#zipTree(Object)} or {@link org.gradle.api.Project#tarTree(Object)}.
  * </p>
  */
+ at HasInternalProtocol
 public interface FileTree extends FileCollection {
     /**
      * <p>Restricts the contents of this tree to those files matching the given filter. The filtered tree is live, so
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 a14512e..50e331b 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
@@ -302,12 +302,8 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public final void execute() {
-        executeWithoutThrowingTaskFailure();
-        state.rethrowFailure();
-    }
-
-    public void executeWithoutThrowingTaskFailure() {
         getExecuter().execute(this, state, new DefaultTaskExecutionContext());
+        state.rethrowFailure();
     }
 
     public TaskExecuter getExecuter() {
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 d27558a..1cfd8a3 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,27 +15,21 @@
  */
 package org.gradle.api.internal;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import groovy.lang.Closure;
 import org.gradle.api.*;
+import org.gradle.internal.Cast;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.model.internal.core.NamedEntityInstantiator;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorphicDomainObjectContainer<T>
         implements ExtensiblePolymorphicDomainObjectContainer<T> {
-    protected final Map<Class<? extends T>, NamedDomainObjectFactory<? extends T>> factories = Maps.newHashMap();
-    private final NamedEntityInstantiator<T> instantiator = new DefaultNamedEntityInstantiator();
+    protected final DefaultPolymorphicNamedEntityInstantiator<T> namedEntityInstantiator;
 
     public DefaultPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
         super(type, instantiator, namer);
+        namedEntityInstantiator = new DefaultPolymorphicNamedEntityInstantiator<T>(type, "this container");
     }
 
     public DefaultPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
@@ -44,43 +38,34 @@ public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorp
 
     @Override
     public NamedEntityInstantiator<T> getEntityInstantiator() {
-        return instantiator;
+        return namedEntityInstantiator;
     }
 
     protected T doCreate(String name) {
-        NamedDomainObjectFactory<? extends T> factory = factories.get(getType());
-        if (factory == null) {
-            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()));
+        try {
+            return namedEntityInstantiator.create(name, getType());
+        } catch (InvalidUserDataException e) {
+            if (e.getCause() instanceof NoFactoryRegisteredForTypeException) {
+                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(), namedEntityInstantiator.getSupportedTypeNames()));
+            } else {
+                throw e;
+            }
         }
-        return factory.create(name);
     }
 
     protected <U extends T> U doCreate(String name, Class<U> type) {
-        @SuppressWarnings("unchecked")
-        NamedDomainObjectFactory<U> factory = (NamedDomainObjectFactory<U>) factories.get(type);
-        if (factory == null) {
-            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);
+        return namedEntityInstantiator.create(name, type);
     }
 
     public <U extends T> void registerDefaultFactory(NamedDomainObjectFactory<U> factory) {
-        factories.put(getType(), factory);
+        Class<T> castType = Cast.uncheckedCast(getType());
+        registerFactory(castType, factory);
     }
 
     public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
-        if (!getType().isAssignableFrom(type)) {
-            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()));
-        }
-        if(factories.containsKey(type)){
-            throw new GradleException(String.format("Cannot register a factory for type %s because "
-                    + "a factory for this type is already registered.", type.getSimpleName()));
-        }
-        factories.put(type, factory);
+        namedEntityInstantiator.registerFactory(type, factory);
     }
 
     public <U extends T> void registerFactory(Class<U> type, final Closure<? extends U> factory) {
@@ -101,23 +86,7 @@ public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorp
         });
     }
 
-    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);
-    }
-
     public Set<? extends Class<? extends T>> getCreateableTypes() {
-        return ImmutableSet.copyOf(factories.keySet());
-    }
-
-    private class DefaultNamedEntityInstantiator implements NamedEntityInstantiator<T> {
-        @Override
-        public <S extends T> S create(String name, Class<S> type) {
-            return doCreate(name, type);
-        }
+        return namedEntityInstantiator.getCreatableTypes();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiator.java
new file mode 100644
index 0000000..f134f18
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+public class DefaultPolymorphicNamedEntityInstantiator<T> implements PolymorphicNamedEntityInstantiator<T> {
+    private final Map<Class<? extends T>, NamedDomainObjectFactory<? extends T>> factories = Maps.newHashMap();
+    private final Class<? extends T> baseType;
+    private final String displayName;
+
+    public DefaultPolymorphicNamedEntityInstantiator(Class<? extends T> type, String displayName) {
+        this.displayName = displayName;
+        this.baseType = type;
+    }
+
+    public <S extends T> S create(String name, Class<S> type) {
+        @SuppressWarnings("unchecked")
+        NamedDomainObjectFactory<S> factory = (NamedDomainObjectFactory<S>) factories.get(type);
+        if (factory == null) {
+            throw new InvalidUserDataException(
+                    String.format("Cannot create a %s because this type is not known to %s. Known types are: %s", type.getSimpleName(), displayName, getSupportedTypeNames()),
+                    new NoFactoryRegisteredForTypeException());
+        }
+        return factory.create(name);
+    }
+
+    public 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);
+    }
+
+    @Override
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+        if (!baseType.isAssignableFrom(type)) {
+            String message = String.format("Cannot register a factory for type %s because it is not a subtype of container element type %s.", type.getSimpleName(), baseType.getSimpleName());
+            throw new IllegalArgumentException(message);
+        }
+        if(factories.containsKey(type)){
+            throw new GradleException(String.format("Cannot register a factory for type %s because a factory for this type is already registered.", type.getSimpleName()));
+        }
+        factories.put(type, factory);
+    }
+
+    @Override
+    public Set<? extends Class<? extends T>> getCreatableTypes() {
+        return ImmutableSet.copyOf(factories.keySet());
+    }
+
+    public void copyFactoriesFrom(DefaultPolymorphicNamedEntityInstantiator<T> source) {
+        for (Class<? extends T> languageType : source.factories.keySet()) {
+            copyFactory(source, languageType);
+        }
+    }
+
+    <U extends T> void copyFactory(DefaultPolymorphicNamedEntityInstantiator<T> source, Class<U> type) {
+        NamedDomainObjectFactory<U> factory = uncheckedCast(source.factories.get(type));
+        registerFactory(type, factory);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensiblePolymorphicDomainObjectContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensiblePolymorphicDomainObjectContainerInternal.java
new file mode 100644
index 0000000..3b554bf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensiblePolymorphicDomainObjectContainerInternal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ExtensiblePolymorphicDomainObjectContainer;
+
+public interface ExtensiblePolymorphicDomainObjectContainerInternal<T> extends ExtensiblePolymorphicDomainObjectContainer<T>, PolymorphicDomainObjectContainerInternal<T> {
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NoFactoryRegisteredForTypeException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NoFactoryRegisteredForTypeException.java
new file mode 100644
index 0000000..5ac3798
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NoFactoryRegisteredForTypeException.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal;
+
+public class NoFactoryRegisteredForTypeException extends RuntimeException {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicNamedEntityInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicNamedEntityInstantiator.java
new file mode 100644
index 0000000..b815a38
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicNamedEntityInstantiator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal;
+
+import org.gradle.api.internal.rules.NamedDomainObjectFactoryRegistry;
+import org.gradle.model.internal.core.NamedEntityInstantiator;
+
+import java.util.Set;
+
+public interface PolymorphicNamedEntityInstantiator<T> extends NamedEntityInstantiator<T>, NamedDomainObjectFactoryRegistry<T> {
+    Set<? extends Class<? extends T>> getCreatableTypes();
+}
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 24099bb..954fd21 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
@@ -40,8 +40,6 @@ public interface TaskInternal extends Task, Configurable<Task> {
 
     void execute();
 
-    void executeWithoutThrowingTaskFailure();
-
     StandardOutputCapture getStandardOutputCapture();
 
     TaskExecuter getExecuter();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencySubstitutionInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencySubstitutionInternal.java
index 7da7871..16e9b58 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencySubstitutionInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencySubstitutionInternal.java
@@ -21,8 +21,7 @@ import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.component.ComponentSelector;
 import org.gradle.api.artifacts.result.ComponentSelectionReason;
 
-public interface DependencySubstitutionInternal<T extends ComponentSelector> extends DependencySubstitution<T> {
-
+public interface DependencySubstitutionInternal extends DependencySubstitution {
     ModuleVersionSelector getOldRequested();
 
     void useTarget(Object notation, ComponentSelectionReason selectionReason);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleDependencySubstitutionInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleDependencySubstitutionInternal.java
deleted file mode 100644
index 36b9333..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleDependencySubstitutionInternal.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ModuleDependencySubstitution;
-import org.gradle.api.artifacts.component.ModuleComponentSelector;
-import org.gradle.api.artifacts.result.ComponentSelectionReason;
-
-public interface ModuleDependencySubstitutionInternal extends ModuleDependencySubstitution, DependencySubstitutionInternal<ModuleComponentSelector> {
-    void useVersion(String version, ComponentSelectionReason selectionReason);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependencySubstitutionInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependencySubstitutionInternal.java
deleted file mode 100644
index 2b07a4b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependencySubstitutionInternal.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ProjectDependencySubstitution;
-import org.gradle.api.artifacts.component.ProjectComponentSelector;
-
-public interface ProjectDependencySubstitutionInternal extends ProjectDependencySubstitution, DependencySubstitutionInternal<ProjectComponentSelector> {
-}
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 62c2acd..9e7d3bc 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
@@ -32,7 +32,7 @@ import java.io.File;
 import java.util.Set;
 
 public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependencyInternal {
-    private ProjectInternal dependencyProject;
+    private final ProjectInternal dependencyProject;
     private final boolean buildProjectDependencies;
     private final TaskDependencyImpl taskDependency = new TaskDependencyImpl();
     private final ProjectAccessListener projectAccessListener;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java
index f284768..4d896ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java
@@ -20,23 +20,25 @@ import org.gradle.api.artifacts.FileCollectionDependency;
 import org.gradle.api.artifacts.SelfResolvingDependency;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.file.FileCollectionInternal;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.tasks.TaskDependency;
 
 import java.io.File;
 import java.util.Set;
 
 public class DefaultSelfResolvingDependency extends AbstractDependency implements SelfResolvingDependency,
-        FileCollectionDependency {
-    private final FileCollection source;
+    FileCollectionDependency {
+    private final FileCollectionInternal source;
 
-    public DefaultSelfResolvingDependency(FileCollection source) {
+    public DefaultSelfResolvingDependency(FileCollectionInternal source) {
         this.source = source;
     }
 
     public FileCollection getSource() {
         return source;
     }
-    
+
     public boolean contentEquals(Dependency dependency) {
         if (!(dependency instanceof DefaultSelfResolvingDependency)) {
             return false;
@@ -77,4 +79,9 @@ public class DefaultSelfResolvingDependency extends AbstractDependency implement
     public TaskDependency getBuildDependencies() {
         return source.getBuildDependencies();
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        source.registerWatchPoints(builder);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
index e2ac907..005ed77 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
@@ -19,20 +19,22 @@ import groovy.lang.Closure;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.file.collections.*;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.internal.file.collections.FileBackedDirectoryFileTree;
+import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
+import org.gradle.api.internal.file.collections.ResolvableFileCollectionResolveContext;
 import org.gradle.api.internal.tasks.DefaultTaskDependency;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskDependency;
-import org.gradle.api.tasks.util.PatternSet;
-import org.gradle.util.GUtil;
 import org.gradle.util.CollectionUtils;
+import org.gradle.util.GUtil;
 
 import java.io.File;
 import java.util.*;
 
-public abstract class AbstractFileCollection implements FileCollection, MinimalFileSet {
+public abstract class AbstractFileCollection implements FileCollectionInternal {
     /**
      * Returns the display name of this file collection. Used in log and error messages.
      *
@@ -125,9 +127,7 @@ public abstract class AbstractFileCollection implements FileCollection, MinimalF
         List<DirectoryFileTree> fileTrees = new ArrayList<DirectoryFileTree>();
         for (File file : getFiles()) {
             if (file.isFile()) {
-                PatternSet patternSet = new PatternSet();
-                patternSet.include(new String[]{file.getName()});
-                fileTrees.add(new DirectoryFileTree(file.getParentFile(), patternSet));
+                fileTrees.add(new FileBackedDirectoryFileTree(file));
             }
         }
         return fileTrees;
@@ -217,4 +217,12 @@ public abstract class AbstractFileCollection implements FileCollection, MinimalF
     protected String getCapDisplayName() {
         return StringUtils.capitalize(getDisplayName());
     }
+
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        for (File file : getFiles()) {
+            builder.add(file);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
index fad52f4..d088ac8 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
@@ -23,6 +23,7 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.resources.ReadableResource;
+import org.gradle.internal.Cast;
 import org.gradle.internal.Factory;
 import org.gradle.internal.exceptions.DiagnosticsVisitor;
 import org.gradle.internal.nativeintegration.filesystem.FileSystem;
@@ -238,19 +239,19 @@ public abstract class AbstractFileResolver implements FileResolver {
         }
     }
 
-    public FileCollection resolveFiles(Object... paths) {
+    public FileCollectionInternal resolveFiles(Object... paths) {
         if (paths.length == 1 && paths[0] instanceof FileCollection) {
-            return (FileCollection) paths[0];
+            return Cast.cast(FileCollectionInternal.class, paths[0]);
         }
         return new DefaultConfigurableFileCollection(this, null, paths);
     }
 
-    public FileTree resolveFilesAsTree(Object... paths) {
-        return resolveFiles(paths).getAsFileTree();
+    public FileTreeInternal resolveFilesAsTree(Object... paths) {
+        return Cast.cast(FileTreeInternal.class, resolveFiles(paths).getAsFileTree());
     }
 
-    public FileTree compositeFileTree(List<FileTree> fileTrees) {
-        return new DefaultCompositeFileTree(fileTrees);
+    public FileTreeInternal compositeFileTree(List<? extends FileTree> fileTrees) {
+        return new DefaultCompositeFileTree(CollectionUtils.checkedCast(FileTreeInternal.class, fileTrees));
     }
 
     public ReadableResource resolveResource(Object path) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java
index 8c04bf7..e85a1bb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java
@@ -22,6 +22,7 @@ import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.Cast;
 import org.gradle.util.ConfigureUtil;
 
 import java.io.File;
@@ -31,7 +32,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-public abstract class AbstractFileTree extends AbstractFileCollection implements FileTree {
+public abstract class AbstractFileTree extends AbstractFileCollection implements FileTreeInternal {
     public Set<File> getFiles() {
         final Set<File> files = new LinkedHashSet<File>();
         visit(new EmptyFileVisitor() {
@@ -106,11 +107,11 @@ public abstract class AbstractFileTree extends AbstractFileCollection implements
     }
 
     public FileTree plus(FileTree fileTree) {
-        return new UnionFileTree(this, fileTree);
+        return new UnionFileTree(this, Cast.cast(FileTreeInternal.class, fileTree));
     }
 
     public FileTree visit(Closure closure) {
-        return visit((FileVisitor) DefaultGroovyMethods.asType(closure, FileVisitor.class));
+        return visit(DefaultGroovyMethods.asType(closure, FileVisitor.class));
     }
 
     private static class FilteredFileTree extends AbstractFileTree {
@@ -148,6 +149,12 @@ public abstract class AbstractFileTree extends AbstractFileCollection implements
             });
             return this;
         }
+
+        @Override
+        public void registerWatchPoints(FileSystemSubset.Builder builder) {
+            // TODO: we aren't considering the filter
+            fileTree.registerWatchPoints(builder);
+        }
     }
 
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy
deleted file mode 100644
index 8e402cc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file
-
-import org.gradle.api.tasks.AntBuilderAware
-import org.gradle.api.internal.file.collections.DirectoryFileTree
-
-class AntFileCollectionMatchingTaskBuilder implements AntBuilderAware {
-    private final Iterable<DirectoryFileTree> fileTrees
-
-    def AntFileCollectionMatchingTaskBuilder(Iterable<DirectoryFileTree> fileTrees) {
-        this.fileTrees = fileTrees
-    }
-
-    def addToAntBuilder(Object node, String childNodeName) {
-        def existing = fileTrees.findAll { it.dir.exists()}
-        existing.each {DirectoryFileTree fileTree ->
-            node."$childNodeName"(location: fileTree.dir)
-        }
-        node.or {
-            existing.each {DirectoryFileTree fileTree ->
-                and {
-                    gradleBaseDirSelector(baseDir: fileTree.dir)
-                    fileTree.patternSet.addToAntBuilder(node, null)
-                }
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.java
new file mode 100644
index 0000000..b7d18f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
+import groovy.lang.Closure;
+import org.gradle.api.internal.BeanDynamicObject;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.tasks.AntBuilderAware;
+
+import java.util.Collections;
+
+public class AntFileCollectionMatchingTaskBuilder implements AntBuilderAware {
+
+    private final Iterable<DirectoryFileTree> fileTrees;
+
+    public AntFileCollectionMatchingTaskBuilder(Iterable<DirectoryFileTree> fileTrees) {
+        this.fileTrees = fileTrees;
+    }
+
+    public Object addToAntBuilder(final Object node, final String childNodeName) {
+        final DynamicObject dynamicObject = new BeanDynamicObject(node);
+
+        final Iterable<DirectoryFileTree> existing = Lists.newLinkedList(
+                FluentIterable
+                        .from(fileTrees)
+                        .filter(new Predicate<DirectoryFileTree>() {
+                            @Override
+                            public boolean apply(DirectoryFileTree input) {
+                                return input.getDir().exists();
+                            }
+                        })
+        );
+
+        for (DirectoryFileTree fileTree : existing) {
+            dynamicObject.invokeMethod(childNodeName, Collections.singletonMap("location", fileTree.getDir()));
+        }
+
+        dynamicObject.invokeMethod("or", new Closure<Void>(this) {
+            public Object doCall(Object ignore) {
+                for (final DirectoryFileTree fileTree : existing) {
+                    dynamicObject.invokeMethod("and", new Closure<Void>(this) {
+                        public Object doCall(Object ignore) {
+                            dynamicObject.invokeMethod("gradleBaseDirSelector", Collections.singletonMap("baseDir", fileTree.getDir()));
+                            fileTree.getPatterns().addToAntBuilder(node, null);
+                            return null;
+                        }
+                    });
+                }
+                return null;
+            }
+        });
+
+        return node;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java
index 2d554f0..4b4d381 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java
@@ -138,11 +138,19 @@ public abstract class CompositeFileCollection extends AbstractFileCollection imp
         }
     }
 
-    protected List<? extends FileCollection> getSourceCollections() {
+    protected Collection<? extends FileCollectionInternal> getSourceCollections() {
         DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext();
         resolve(context);
         return context.resolveAsFileCollections();
     }
 
     public abstract void resolve(FileCollectionResolveContext context);
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        for (FileCollectionInternal files : getSourceCollections()) {
+            files.registerWatchPoints(builder);
+        }
+    }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java
index 2fb6fb1..2c534a1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java
@@ -22,16 +22,17 @@ import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
 import org.gradle.api.internal.file.collections.ResolvableFileCollectionResolveContext;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.internal.Cast;
 
-import java.util.List;
+import java.util.Collection;
 
-public abstract class CompositeFileTree extends CompositeFileCollection implements FileTree {
-    protected List<FileTree> getSourceCollections() {
-        return (List) super.getSourceCollections();
+public abstract class CompositeFileTree extends CompositeFileCollection implements FileTreeInternal {
+    protected Collection<? extends FileTreeInternal> getSourceCollections() {
+        return Cast.uncheckedCast(super.getSourceCollections());
     }
 
     public FileTree plus(FileTree fileTree) {
-        return new UnionFileTree(this, fileTree);
+        return new UnionFileTree(this, Cast.cast(FileTreeInternal.class, fileTree));
     }
 
     public FileTree matching(Closure filterConfigClosure) {
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
index 19aa306..ad42675 100644
--- 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
@@ -16,15 +16,14 @@
 
 package org.gradle.api.internal.file;
 
-import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
 
-import java.util.List;
+import java.util.Collection;
 
 public class DefaultCompositeFileTree extends CompositeFileTree {
-    private final List<FileTree> fileTrees;
+    private final Collection<? extends FileTreeInternal> fileTrees;
 
-    public DefaultCompositeFileTree(List<FileTree> fileTrees) {
+    public DefaultCompositeFileTree(Collection<? extends FileTreeInternal> fileTrees) {
         this.fileTrees = fileTrees;
     }
 
@@ -34,7 +33,7 @@ public class DefaultCompositeFileTree extends CompositeFileTree {
     }
 
     @Override
-    protected List<FileTree> getSourceCollections() {
+    protected Collection<? extends FileTreeInternal> getSourceCollections() {
         return fileTrees;
     }
 
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 6e47ef9..edcbe95 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
@@ -97,9 +97,15 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
     }
 
     public FileTree tarTree(Object tarPath) {
-        ReadableResource res = getResources().maybeCompressed(tarPath);
-
-        TarFileTree tarTree = new TarFileTree(res, getExpandDir(), fileSystem);
+        File tarFile = null;
+        ReadableResource resource;
+        if (tarPath instanceof ReadableResource) {
+            resource = (ReadableResource) tarPath;
+        } else {
+            tarFile = file(tarPath);
+            resource = new FileResource(tarFile);
+        }
+        TarFileTree tarTree = new TarFileTree(tarFile, new MaybeCompressedFileResource(resource), getExpandDir(), fileSystem);
         return new FileTreeAdapter(tarTree);
     }
 
@@ -133,11 +139,16 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
     }
 
     public CopySpec copySpec(Action<? super CopySpec> action) {
-        DefaultCopySpec copySpec = instantiator.newInstance(DefaultCopySpec.class, fileResolver, instantiator);
+        CopySpec copySpec = copySpec();
         action.execute(copySpec);
         return copySpec;
     }
 
+    @Override
+    public CopySpec copySpec() {
+        return instantiator.newInstance(DefaultCopySpec.class, fileResolver, instantiator);
+    }
+
     public FileResolver getFileResolver() {
         return fileResolver;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
index 7a3db70..83e3233 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
@@ -152,10 +152,8 @@ public class DefaultSourceDirectorySet extends CompositeFileTree implements Sour
 
     @Override
     public void resolve(FileCollectionResolveContext context) {
-        for (DirectoryTree directoryTree : getSrcDirTrees()) {
-            if (directoryTree.getDir().isDirectory()) {
-                context.add(((DirectoryFileTree) directoryTree).filter(filter));
-            }
+        for (DirectoryTree directoryTree : doGetSrcDirTrees()) {
+            context.add(((DirectoryFileTree) directoryTree).filter(filter));
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileCollectionInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileCollectionInternal.java
new file mode 100644
index 0000000..4692f3f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileCollectionInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.MinimalFileSet;
+
+public interface FileCollectionInternal extends FileCollection, MinimalFileSet {
+
+    /**
+     * Adds a logical description of the potential contents of this collection to the builder.
+     * <p>
+     * That is, registers a description of the parts of the file system that can influence the actual contents of the collection.
+     * <p>
+     * It is not required that an absolutely accurate description is added.
+     * For example, the description added to the builder may not consider all kinds of filtering that the file collection actually applies.
+     *
+     * @param builder the receiver of the description.
+     */
+    void registerWatchPoints(FileSystemSubset.Builder builder);
+
+}
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 bfdcaa7..aae39d2 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
@@ -49,7 +49,7 @@ public interface FileOperations {
 
     FileTree tarTree(Object tarPath);
 
-    CopySpec copySpec(Action<? super CopySpec> action);
+    CopySpec copySpec();
 
     WorkResult copy(Action<? super CopySpec> action);
 
@@ -60,4 +60,4 @@ public interface FileOperations {
     boolean delete(Object... paths);
 
     ResourceHandler getResources();
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
index 434cd32..b39f3fb 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
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.PathValidation;
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
@@ -34,12 +33,12 @@ public interface FileResolver {
     File resolve(Object path, PathValidation validation);
 
     Factory<File> resolveLater(Object path);
-    
-    FileCollection resolveFiles(Object... paths);
 
-    FileTree resolveFilesAsTree(Object... paths);
+    FileCollectionInternal resolveFiles(Object... paths);
 
-    FileTree compositeFileTree(List<FileTree> fileTrees);
+    FileTreeInternal resolveFilesAsTree(Object... paths);
+
+    FileTreeInternal compositeFileTree(List<? extends FileTree> fileTrees);
 
     URI resolveUri(Object path);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSystemSubset.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSystemSubset.java
new file mode 100644
index 0000000..c81725b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSystemSubset.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.google.common.base.Function;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.file.DirectoryTree;
+import org.gradle.api.internal.file.collections.DirectoryTrees;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.FileUtils;
+import org.gradle.internal.nativeintegration.services.FileSystems;
+
+import java.io.File;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Represents a subset of the *potential* file system space.
+ * <p>
+ * No regard is given to what the actual file system represented by this actually looks like.
+ * That is, no attention is paid to whether a particular file is a directory, regular file or anything else.
+ * Instances do not access the filesystem.
+ */
+ at ThreadSafe
+public class FileSystemSubset {
+
+    private final ImmutableCollection<File> files;
+    private final ImmutableCollection<ImmutableDirectoryTree> trees;
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public FileSystemSubset(ImmutableCollection<File> files, ImmutableCollection<ImmutableDirectoryTree> trees) {
+        this.files = files;
+        this.trees = trees;
+    }
+
+    public Iterable<? extends File> getRoots() {
+        return FileUtils.calculateRoots(
+            Iterables.concat(files, Iterables.transform(trees, new Function<DirectoryTree, File>() {
+                @Override
+                public File apply(DirectoryTree input) {
+                    return input.getDir();
+                }
+            }))
+        );
+    }
+
+    public FileSystemSubset unfiltered() {
+        return new FileSystemSubset(ImmutableList.copyOf(getRoots()), ImmutableList.<ImmutableDirectoryTree>of());
+    }
+
+    public boolean isEmpty() {
+        return files.isEmpty() && trees.isEmpty();
+    }
+
+    public boolean contains(File file) {
+        File absoluteFile = file.getAbsoluteFile();
+        String pathWithSeparator = file.getAbsolutePath() + File.separator;
+        for (File candidateFile : files) {
+            String candidateFilePathWithSeparator = candidateFile.getPath() + File.separator;
+            if (pathWithSeparator.startsWith(candidateFilePathWithSeparator)) {
+                return true;
+            }
+        }
+
+        for (DirectoryTree tree : trees) {
+            if (tree.getDir().getAbsoluteFile().equals(absoluteFile) || DirectoryTrees.contains(FileSystems.getDefault(), tree, absoluteFile)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Files: ").append(files);
+        sb.append(" Trees: ").append(trees);
+        return sb.toString();
+    }
+
+    @ThreadSafe
+    public static class Builder {
+        private final ImmutableSet.Builder<File> files = ImmutableSet.builder();
+        private final ImmutableSet.Builder<ImmutableDirectoryTree> trees = ImmutableSet.builder();
+        private final Lock lock = new ReentrantLock();
+
+        private Builder() {
+        }
+
+        public Builder add(File file) {
+            lock.lock();
+            try {
+                files.add(file.getAbsoluteFile());
+                return this;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public Builder add(DirectoryTree directoryTree) {
+            lock.lock();
+            try {
+                trees.add(ImmutableDirectoryTree.of(directoryTree));
+                return this;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public Builder add(File dir, PatternSet patternSet) {
+            lock.lock();
+            try {
+                trees.add(ImmutableDirectoryTree.of(dir, patternSet));
+                return this;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public FileSystemSubset build() {
+            lock.lock();
+            try {
+                return new FileSystemSubset(files.build(), trees.build());
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileTreeInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileTreeInternal.java
new file mode 100644
index 0000000..dcf3eaa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileTreeInternal.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+public interface FileTreeInternal extends FileTree, FileCollectionInternal {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutableDirectoryTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutableDirectoryTree.java
new file mode 100644
index 0000000..ea04e9f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutableDirectoryTree.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.DirectoryTree;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+
+// Not quite immutable, see ImmutablePatternSet
+public final class ImmutableDirectoryTree implements DirectoryTree {
+
+    private final File dir;
+    private final ImmutablePatternSet patternSet;
+
+    public static ImmutableDirectoryTree of(DirectoryTree source) {
+        if (source instanceof ImmutableDirectoryTree) {
+            return (ImmutableDirectoryTree) source;
+        } else {
+            return of(source.getDir(), source.getPatterns());
+        }
+    }
+
+    public static ImmutableDirectoryTree of(File dir, PatternSet patternSet) {
+        return new ImmutableDirectoryTree(dir, patternSet);
+    }
+
+    private ImmutableDirectoryTree(File dir, PatternSet patternSet) {
+        this.dir = dir;
+        this.patternSet = ImmutablePatternSet.of(patternSet);
+    }
+
+    @Override
+    public File getDir() {
+        return dir;
+    }
+
+    @Override
+    public ImmutablePatternSet getPatterns() {
+        return patternSet;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ImmutableDirectoryTree that = (ImmutableDirectoryTree) o;
+
+        if (dir != null ? !dir.equals(that.dir) : that.dir != null) {
+            return false;
+        }
+        return !(patternSet != null ? !patternSet.equals(that.patternSet) : that.patternSet != null);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = dir != null ? dir.hashCode() : 0;
+        result = 31 * result + (patternSet != null ? patternSet.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "dir: " + dir.getPath();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutablePatternSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutablePatternSet.java
new file mode 100644
index 0000000..b870c6e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/ImmutablePatternSet.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternSet;
+
+// TODO: this doesn't quite guarantee immutability, because the source may be holding closures that are doing god knows what
+public class ImmutablePatternSet extends PatternSet {
+
+    public static ImmutablePatternSet of(PatternSet source) {
+        if (source instanceof ImmutablePatternSet) {
+            return (ImmutablePatternSet) source;
+        } else {
+            return new ImmutablePatternSet(source);
+        }
+    }
+
+    private ImmutablePatternSet(PatternSet source) {
+        doCopyFrom(source);
+    }
+
+    @Override
+    public PatternSet setIncludes(Iterable<String> includes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet include(String... includes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet include(Iterable includes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet include(Spec<FileTreeElement> spec) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setCaseSensitive(boolean caseSensitive) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet setExcludes(Iterable<String> excludes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet includeSpecs(Iterable<Spec<FileTreeElement>> includeSpecs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet include(Closure closure) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet exclude(String... excludes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet exclude(Iterable excludes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet excludeSpecs(Iterable<Spec<FileTreeElement>> excludes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet exclude(Spec<FileTreeElement> spec) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PatternSet exclude(Closure closure) {
+        throw new UnsupportedOperationException();
+    }
+}
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 27fee31..094e369 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
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file;
 
 import org.apache.commons.io.FilenameUtils;
 import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.CompressedReadableResource;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.resources.MissingResourceException;
 import org.gradle.api.resources.ReadableResource;
@@ -31,14 +32,20 @@ public class MaybeCompressedFileResource implements ReadableResource {
     private final ReadableResource resource;
 
     public MaybeCompressedFileResource(ReadableResource resource) {
-        String ext = FilenameUtils.getExtension(resource.getURI().toString());
-
-        if (Compression.BZIP2.getSupportedExtensions().contains(ext)) {
-            this.resource = new Bzip2Archiver(resource);
-        } else if (Compression.GZIP.getSupportedExtensions().contains(ext)) {
-            this.resource = new GzipArchiver(resource);
-        } else {
+        if (resource instanceof CompressedReadableResource) {
+            // Already in something to uncompress it
             this.resource = resource;
+        } else {
+            String ext = FilenameUtils.getExtension(resource.getURI().toString());
+
+            if (Compression.BZIP2.getSupportedExtensions().contains(ext)) {
+                this.resource = new Bzip2Archiver(resource);
+            } else if (Compression.GZIP.getSupportedExtensions().contains(ext)) {
+                this.resource = new GzipArchiver(resource);
+            } else {
+                // Unrecognized extension
+                this.resource = resource;
+            }
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java
index f791180..9876da4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java
@@ -15,30 +15,31 @@
  */
 package org.gradle.api.internal.file;
 
-import org.gradle.api.file.FileTree;
+import com.google.common.collect.Sets;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
+import org.gradle.internal.Cast;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.LinkedHashSet;
 import java.util.Set;
 
 public class UnionFileTree extends CompositeFileTree {
-    private final Set<FileTree> sourceTrees;
+    private final Set<FileTreeInternal> sourceTrees;
     private final String displayName;
 
-    public UnionFileTree(FileTree... sourceTrees) {
+    public UnionFileTree(FileTreeInternal... sourceTrees) {
         this("file tree", Arrays.asList(sourceTrees));
     }
 
-    public UnionFileTree(String displayName, FileTree... sourceTrees) {
+    public UnionFileTree(String displayName, FileTreeInternal... sourceTrees) {
         this(displayName, Arrays.asList(sourceTrees));
     }
 
-    public UnionFileTree(String displayName, Collection<? extends FileTree> sourceTrees) {
+    public UnionFileTree(String displayName, Collection<? extends FileTreeInternal> sourceTrees) {
         this.displayName = displayName;
-        this.sourceTrees = new LinkedHashSet<FileTree>(sourceTrees);
+        this.sourceTrees = Sets.newLinkedHashSet(sourceTrees);
     }
 
     @Override
@@ -56,8 +57,8 @@ public class UnionFileTree extends CompositeFileTree {
         if (!(source instanceof FileTree)) {
             throw new UnsupportedOperationException(String.format("Can only add FileTree instances to %s.", getDisplayName()));
         }
-        
-        sourceTrees.add((FileTree) source);
+
+        sourceTrees.add(Cast.cast(FileTreeInternal.class, source));
         return this;
     }
 }
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 17c5999..f4b6461 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
@@ -19,10 +19,12 @@ import org.apache.tools.tar.TarEntry;
 import org.apache.tools.tar.TarInputStream;
 import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Nullable;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileSystemMirroringFileTree;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
@@ -38,11 +40,13 @@ import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
+    private final File tarFile;
     private final ReadableResource resource;
     private final Chmod chmod;
     private final File tmpDir;
 
-    public TarFileTree(ReadableResource resource, File tmpDir, Chmod chmod) {
+    public TarFileTree(@Nullable File tarFile, ReadableResource resource, File tmpDir, Chmod chmod) {
+        this.tarFile = tarFile;
         this.resource = resource;
         this.chmod = chmod;
         String expandDirName = String.format("%s_%s", resource.getBaseName(), HashUtil.createCompactMD5(resource.getURI().toString()));
@@ -73,9 +77,9 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             }
         } catch (Exception e) {
             String message = "Unable to expand " + getDisplayName() + "\n"
-                    + "  The tar might be corrupted or it is compressed in an unexpected way.\n"
-                    + "  By default the tar tree tries to guess the compression based on the file extension.\n"
-                    + "  If you need to specify the compression explicitly please refer to the DSL reference.";
+                + "  The tar might be corrupted or it is compressed in an unexpected way.\n"
+                + "  By default the tar tree tries to guess the compression based on the file extension.\n"
+                + "  If you need to specify the compression explicitly please refer to the DSL reference.";
             throw new GradleException(message, e);
         }
     }
@@ -168,4 +172,11 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             return currEntry;
         }
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        if (tarFile != null) {
+            builder.add(tarFile);
+        }
+    }
 }
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 a7c71d5..62c3cd4 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
@@ -24,6 +24,7 @@ import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileSystemMirroringFileTree;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
@@ -139,7 +140,7 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             return entry.getSize();
         }
 
-        public InputStream open()  {
+        public InputStream open() {
             try {
                 return zip.getInputStream(entry);
             } catch (IOException e) {
@@ -153,15 +154,20 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
 
         public int getMode() {
             int unixMode = entry.getUnixMode() & 0777;
-            if(unixMode == 0){
+            if (unixMode == 0) {
                 //no mode infos available - fall back to defaults
-                if(isDirectory()){
+                if (isDirectory()) {
                     unixMode = FileSystem.DEFAULT_DIR_MODE;
-                }else{
+                } else {
                     unixMode = FileSystem.DEFAULT_FILE_MODE;
                 }
             }
             return unixMode;
         }
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        builder.add(zipFile);
+    }
 }
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 fb4f1d6..18d0c96 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,7 +28,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
 
-public class Bzip2Archiver implements ReadableResource {
+public class Bzip2Archiver implements CompressedReadableResource {
 
     private final ReadableResource resource;
     private final URI uri;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/CompressedReadableResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/CompressedReadableResource.java
new file mode 100644
index 0000000..3cc9239
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/CompressedReadableResource.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.archive.compression;
+
+import org.gradle.api.resources.ReadableResource;
+
+public interface CompressedReadableResource extends ReadableResource {
+}
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 4cef989..6f31b58 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,7 +28,7 @@ import java.net.URI;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
-public class GzipArchiver implements ReadableResource {
+public class GzipArchiver implements CompressedReadableResource {
 
     private ReadableResource resource;
     private URI uri;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/BuildDependenciesOnlyFileCollectionResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/BuildDependenciesOnlyFileCollectionResolveContext.java
index b517596..9f599e4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/BuildDependenciesOnlyFileCollectionResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/BuildDependenciesOnlyFileCollectionResolveContext.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.file.collections;
 
 import org.gradle.api.Buildable;
-import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.api.tasks.TaskDependency;
 
@@ -43,8 +43,8 @@ public class BuildDependenciesOnlyFileCollectionResolveContext extends DefaultFi
         return resolveAsFileCollections();
     }
 
-    private static class BuildableFileTreeInternalConverter implements Converter<FileTree> {
-        public void convertInto(Object element, Collection<? super FileTree> result, FileResolver resolver) {
+    private static class BuildableFileTreeInternalConverter implements Converter<FileTreeInternal> {
+        public void convertInto(Object element, Collection<? super FileTreeInternal> result, FileResolver resolver) {
             if (element instanceof DefaultFileCollectionResolveContext) {
                 DefaultFileCollectionResolveContext nestedContext = (DefaultFileCollectionResolveContext) element;
                 result.addAll(nestedContext.resolveAsFileTrees());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
index 374063a..bd79f75 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
@@ -19,10 +19,13 @@ import groovy.lang.Closure;
 import org.gradle.api.Task;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.TaskOutputs;
+import org.gradle.internal.Cast;
 import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
 
@@ -34,8 +37,8 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
     private final FileResolver fileResolver;
     private final List<Object> queue = new LinkedList<Object>();
     private List<Object> addTo = queue;
-    private final Converter<? extends FileCollection> fileCollectionConverter;
-    private final Converter<? extends FileTree> fileTreeConverter;
+    private final Converter<? extends FileCollectionInternal> fileCollectionConverter;
+    private final Converter<? extends FileTreeInternal> fileTreeConverter;
 
     public DefaultFileCollectionResolveContext() {
         this(new IdentityFileResolver());
@@ -47,7 +50,7 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
         fileTreeConverter = new FileTreeConverter();
     }
 
-    protected DefaultFileCollectionResolveContext(FileResolver fileResolver, Converter<? extends FileCollection> fileCollectionConverter, Converter<? extends FileTree> fileTreeConverter) {
+    protected DefaultFileCollectionResolveContext(FileResolver fileResolver, Converter<? extends FileCollectionInternal> fileCollectionConverter, Converter<? extends FileTreeInternal> fileTreeConverter) {
         this.fileResolver = fileResolver;
         this.fileCollectionConverter = fileCollectionConverter;
         this.fileTreeConverter = fileTreeConverter;
@@ -71,14 +74,14 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
     /**
      * Resolves the contents of this context as a list of atomic {@link FileTree} instances.
      */
-    public List<FileTree> resolveAsFileTrees() {
+    public List<FileTreeInternal> resolveAsFileTrees() {
         return doResolve(fileTreeConverter);
     }
 
     /**
      * Resolves the contents of this context as a list of atomic {@link FileCollection} instances.
      */
-    public List<FileCollection> resolveAsFileCollections() {
+    public List<FileCollectionInternal> resolveAsFileCollections() {
         return doResolve(fileCollectionConverter);
     }
 
@@ -150,14 +153,14 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
         void convertInto(Object element, Collection<? super T> result, FileResolver resolver);
     }
 
-    private static class FileCollectionConverter implements Converter<FileCollection> {
-        public void convertInto(Object element, Collection<? super FileCollection> result, FileResolver fileResolver) {
+    private static class FileCollectionConverter implements Converter<FileCollectionInternal> {
+        public void convertInto(Object element, Collection<? super FileCollectionInternal> result, FileResolver fileResolver) {
             if (element instanceof DefaultFileCollectionResolveContext) {
                 DefaultFileCollectionResolveContext nestedContext = (DefaultFileCollectionResolveContext) element;
                 result.addAll(nestedContext.resolveAsFileCollections());
             } else if (element instanceof FileCollection) {
                 FileCollection fileCollection = (FileCollection) element;
-                result.add(fileCollection);
+                result.add(Cast.cast(FileCollectionInternal.class, fileCollection));
             } else if (element instanceof MinimalFileTree) {
                 MinimalFileTree fileTree = (MinimalFileTree) element;
                 result.add(new FileTreeAdapter(fileTree));
@@ -175,14 +178,14 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
         }
     }
 
-    private static class FileTreeConverter implements Converter<FileTree> {
-        public void convertInto(Object element, Collection<? super FileTree> result, FileResolver fileResolver) {
+    private static class FileTreeConverter implements Converter<FileTreeInternal> {
+        public void convertInto(Object element, Collection<? super FileTreeInternal> result, FileResolver fileResolver) {
             if (element instanceof DefaultFileCollectionResolveContext) {
                 DefaultFileCollectionResolveContext nestedContext = (DefaultFileCollectionResolveContext) element;
                 result.addAll(nestedContext.resolveAsFileTrees());
             } else if (element instanceof FileTree) {
                 FileTree fileTree = (FileTree) element;
-                result.add(fileTree);
+                result.add(Cast.cast(FileTreeInternal.class, fileTree));
             } else if (element instanceof MinimalFileTree) {
                 MinimalFileTree fileTree = (MinimalFileTree) element;
                 result.add(new FileTreeAdapter(fileTree));
@@ -201,7 +204,7 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
             }
         }
 
-        private void convertFileToFileTree(File file, Collection<? super FileTree> result) {
+        private void convertFileToFileTree(File file, Collection<? super FileTreeInternal> result) {
             if (file.isDirectory()) {
                 result.add(new FileTreeAdapter(new DirectoryFileTree(file)));
             } else if (file.isFile()) {
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
index ac291ce..808f6f3 100644
--- 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
@@ -18,6 +18,8 @@ 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.internal.file.FileCollectionInternal;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskDependency;
@@ -27,11 +29,10 @@ import java.util.Iterator;
 import java.util.Set;
 
 /**
- * A file collection that delegates each method call to the
- * file collection returned by {@link #getDelegate()}.
+ * 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 abstract class DelegatingFileCollection implements FileCollectionInternal {
+    public abstract FileCollectionInternal getDelegate();
 
     public File getSingleFile() throws IllegalStateException {
         return getDelegate().getSingleFile();
@@ -102,10 +103,11 @@ public abstract class DelegatingFileCollection implements FileCollection, Minima
     }
 
     public String getDisplayName() {
-        FileCollection delegate = getDelegate();
-        if (delegate instanceof MinimalFileSet) {
-            return ((MinimalFileSet) delegate).getDisplayName();
-        }
-        return getDelegate().toString();
+        return getDelegate().getDisplayName();
+    }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        getDelegate().registerWatchPoints(builder);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileTree.java
index 65998e3..dc39dd1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DelegatingFileTree.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.tasks.util.PatternFilterable;
 
@@ -24,8 +25,8 @@ import org.gradle.api.tasks.util.PatternFilterable;
  * A file tree that delegates each method call to the
  * file tree returned by {@link #getDelegate()}.
  */
-public abstract class DelegatingFileTree extends DelegatingFileCollection implements FileTree {
-    public abstract FileTree getDelegate();
+public abstract class DelegatingFileTree extends DelegatingFileCollection implements FileTreeInternal {
+    public abstract FileTreeInternal getDelegate();
 
     public FileTree matching(Closure filterConfigClosure) {
         return getDelegate().matching(filterConfigClosure);
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 36c28cf..440d168 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
@@ -18,8 +18,8 @@ package org.gradle.api.internal.file.collections;
 
 import org.gradle.api.GradleException;
 import org.gradle.api.file.*;
-import org.gradle.api.internal.file.DefaultFileTreeElement;
 import org.gradle.api.internal.file.DefaultFileVisitDetails;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
@@ -36,7 +36,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Pattern;
 
 /**
  * Directory walker supporting {@link Spec}s for includes and excludes.
@@ -47,9 +46,11 @@ import java.util.regex.Pattern;
  * excludes.
  */
 public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFileTree, RandomAccessFileCollection, LocalFileTree, DirectoryTree {
+
     private static final Logger LOGGER = Logging.getLogger(DirectoryFileTree.class);
 
     private final File dir;
+
     private PatternSet patternSet;
     private boolean postfix;
     private final FileSystem fileSystem = FileSystems.getDefault();
@@ -93,16 +94,12 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
     }
 
     public boolean contains(File file) {
-        String prefix = dir.getAbsolutePath() + File.separator;
-        if (!file.getAbsolutePath().startsWith(prefix)) {
-            return false;
-        }
-        if (!file.isFile()) {
-            return false;
-        }
-        RelativePath path = new RelativePath(true, file.getAbsolutePath().substring(prefix.length()).split(
-                Pattern.quote(File.separator)));
-        return patternSet.getAsSpec().isSatisfiedBy(new DefaultFileTreeElement(file, path, fileSystem, fileSystem));
+        return DirectoryTrees.contains(fileSystem, this, file) && file.isFile();
+    }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        builder.add(this);
     }
 
     /**
@@ -187,4 +184,8 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
         postfix = true;
         return this;
     }
+
+    public PatternSet getPatternSet() {
+        return patternSet;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryTrees.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryTrees.java
new file mode 100644
index 0000000..d8c1eee
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryTrees.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.DirectoryTree;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.DefaultFileTreeElement;
+import org.gradle.internal.nativeintegration.filesystem.FileSystem;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+public abstract class DirectoryTrees {
+
+    private static final String QUOTED_SEPARATOR = Pattern.quote(File.separator);
+
+    private DirectoryTrees() {
+    }
+
+    public static boolean contains(FileSystem fileSystem, DirectoryTree tree, File file) {
+        String prefix = tree.getDir().getAbsolutePath() + File.separator;
+        if (!file.getAbsolutePath().startsWith(prefix)) {
+            return false;
+        }
+
+        String[] partsUnderDir = file.getAbsolutePath().substring(prefix.length()).split(QUOTED_SEPARATOR);
+        RelativePath path = new RelativePath(true, partsUnderDir);
+        return tree.getPatterns().getAsSpec().isSatisfiedBy(new DefaultFileTreeElement(file, path, fileSystem, fileSystem));
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/EmptyFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/EmptyFileTree.java
index d2beb27..a225f71 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/EmptyFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/EmptyFileTree.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.file.collections;
 
 import org.gradle.api.Buildable;
 import org.gradle.api.file.FileVisitor;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.tasks.TaskDependency;
 
 import java.util.Collection;
@@ -46,4 +47,9 @@ public class EmptyFileTree implements MinimalFileTree, Buildable, LocalFileTree
 
     public void visit(FileVisitor visitor) {
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileBackedDirectoryFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileBackedDirectoryFileTree.java
new file mode 100644
index 0000000..b5b880e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileBackedDirectoryFileTree.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks.util.PatternSet;
+
+import java.io.File;
+
+public class FileBackedDirectoryFileTree extends DirectoryFileTree {
+    private final File file;
+
+    public FileBackedDirectoryFileTree(File file) {
+        super(file.getParentFile(), new PatternSet().include(file.getName()));
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileTreeAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileTreeAdapter.java
index cd000bd..e2a003c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileTreeAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileTreeAdapter.java
@@ -19,6 +19,7 @@ import org.gradle.api.Buildable;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.internal.file.AbstractFileTree;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.util.PatternFilterable;
 
@@ -50,6 +51,11 @@ public class FileTreeAdapter extends AbstractFileTree implements FileCollectionC
     }
 
     @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        tree.registerWatchPoints(builder);
+    }
+
+    @Override
     protected Collection<DirectoryFileTree> getAsFileTrees() {
         if (tree instanceof FileSystemMirroringFileTree) {
             FileSystemMirroringFileTree mirroringTree = (FileSystemMirroringFileTree) tree;
@@ -80,6 +86,12 @@ public class FileTreeAdapter extends AbstractFileTree implements FileCollectionC
             RandomAccessFileCollection randomAccess = (RandomAccessFileCollection) tree;
             return randomAccess.contains(file);
         }
+        if (tree instanceof MapFileTree) {
+            return ((MapFileTree) tree).getFilesWithoutCreating().contains(file);
+        }
+        if (tree instanceof FileSystemMirroringFileTree) {
+            return ((FileSystemMirroringFileTree) tree).getMirror().contains(file);
+        }
         return super.contains(file);
     }
 
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
index cef9e92..3c624c3 100644
--- 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
@@ -15,18 +15,18 @@
  */
 package org.gradle.api.internal.file.collections;
 
-import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileCollectionInternal;
 
 /**
  * A {@link DelegatingFileCollection} whose delegate is created lazily.
  */
 public abstract class LazilyInitializedFileCollection extends DelegatingFileCollection {
-    private FileCollection delegate;
+    private FileCollectionInternal delegate;
 
-    public abstract FileCollection createDelegate();
+    public abstract FileCollectionInternal createDelegate();
 
     @Override
-    public final synchronized FileCollection getDelegate() {
+    public final synchronized FileCollectionInternal getDelegate() {
         if (delegate == null) {
             delegate = createDelegate();
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileTree.java
index a79a8fb..7ce765a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/LazilyInitializedFileTree.java
@@ -15,18 +15,18 @@
  */
 package org.gradle.api.internal.file.collections;
 
-import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileTreeInternal;
 
 /**
  * A {@link org.gradle.api.internal.file.collections.DelegatingFileTree} whose delegate is created lazily.
  */
 public abstract class LazilyInitializedFileTree extends DelegatingFileTree {
-    private FileTree delegate;
+    private FileTreeInternal delegate;
 
-    public abstract FileTree createDelegate();
+    public abstract FileTreeInternal createDelegate();
 
     @Override
-    public final synchronized FileTree getDelegate() {
+    public final synchronized FileTreeInternal getDelegate() {
         if (delegate == null) {
             delegate = createDelegate();
         }
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 77dc589..10cdd59 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
@@ -17,13 +17,16 @@ package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
 import org.gradle.api.Action;
+import org.gradle.api.Transformer;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.FileSystemSubset;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeintegration.filesystem.Chmod;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.io.InputStream;
@@ -38,21 +41,35 @@ import java.util.concurrent.atomic.AtomicBoolean;
  * A {@link MinimalFileTree} which is composed using a mapping from relative path to file source.
  */
 public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
+    public enum FileCreationMode {
+        OVERWRITE,
+        KEEP_EXISTING
+    }
     private final Map<RelativePath, Action<OutputStream>> elements = new LinkedHashMap<RelativePath, Action<OutputStream>>();
     private final Factory<File> tmpDirSource;
     private final Chmod chmod;
+    private final FileCreationMode fileCreationMode;
 
     public MapFileTree(final File tmpDir, Chmod chmod) {
+        this(tmpDir, chmod, FileCreationMode.OVERWRITE);
+    }
+
+    public MapFileTree(final File tmpDir, Chmod chmod, FileCreationMode fileCreationMode) {
         this(new Factory<File>() {
                 public File create() {
                     return tmpDir;
                 }
-        }, chmod);
+        }, chmod, fileCreationMode);
     }
 
     public MapFileTree(Factory<File> tmpDirSource, Chmod chmod) {
+        this(tmpDirSource, chmod, FileCreationMode.OVERWRITE);
+    }
+
+    public MapFileTree(Factory<File> tmpDirSource, Chmod chmod, FileCreationMode fileCreationMode) {
         this.tmpDirSource = tmpDirSource;
         this.chmod = chmod;
+        this.fileCreationMode = fileCreationMode;
     }
 
     private File getTmpDir() {
@@ -80,6 +97,15 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         }
     }
 
+    public Set<File> getFilesWithoutCreating() {
+        return CollectionUtils.collect(elements.keySet(), new Transformer<File, RelativePath>() {
+            @Override
+            public File transform(RelativePath relativePath) {
+                return createFileInstance(relativePath);
+            }
+        });
+    }
+
     /**
      * Adds an element to this tree. The given closure is passed an OutputStream which it can use to write the content
      * of the element to.
@@ -118,10 +144,14 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         }
     }
 
+    private File createFileInstance(RelativePath path) {
+        return path.getFile(getTmpDir());
+    }
+
     private class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
         private final RelativePath path;
         private final Action<OutputStream> generator;
-        private final long lastModified;
+        private long lastModified;
         private final AtomicBoolean stopFlag;
         private File file;
 
@@ -130,8 +160,6 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             this.path = path;
             this.generator = generator;
             this.stopFlag = stopFlag;
-            // round to nearest second
-            lastModified = System.currentTimeMillis() / 1000 * 1000;
         }
 
         public String getDisplayName() {
@@ -144,8 +172,13 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
 
         public File getFile() {
             if (file == null) {
-                file = path.getFile(getTmpDir());
-                copyTo(file);
+                file = createFileInstance(path);
+                if(fileCreationMode == FileCreationMode.OVERWRITE || !file.exists()) {
+                    copyTo(file);
+                } else {
+                    // round to nearest second
+                    lastModified = file.lastModified() / 1000 * 1000;
+                }
             }
             return file;
         }
@@ -155,6 +188,7 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         }
 
         public long getLastModified() {
+            getFile();
             return lastModified;
         }
 
@@ -163,6 +197,8 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         }
 
         public void copyTo(OutputStream outstr) {
+            // round to nearest second
+            lastModified = System.currentTimeMillis() / 1000 * 1000;
             generator.execute(outstr);
         }
 
@@ -174,4 +210,9 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             return path;
         }
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
index 861a280..6e9e23b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.file.collections;
 
 import org.gradle.api.file.FileVisitor;
+import org.gradle.api.internal.file.FileSystemSubset;
 
 /**
  * A minimal file tree implementation. An implementation can optionally also implement the following interfaces:
@@ -31,4 +32,6 @@ public interface MinimalFileTree extends MinimalFileCollection {
      * Visits the elements of this tree, in depth-first prefix order.
      */
     void visit(FileVisitor visitor);
+
+    void registerWatchPoints(FileSystemSubset.Builder builder);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ResolvableFileCollectionResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ResolvableFileCollectionResolveContext.java
index ca62771..3fa36c6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ResolvableFileCollectionResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ResolvableFileCollectionResolveContext.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.file.collections;
 
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileCollectionInternal;
+import org.gradle.api.internal.file.FileTreeInternal;
 
 import java.util.List;
 
@@ -24,10 +24,10 @@ public interface ResolvableFileCollectionResolveContext extends FileCollectionRe
     /**
      * Resolves the contents of this context as a sequence of atomic file collections.
      */
-    List<FileCollection> resolveAsFileCollections();
+    List<FileCollectionInternal> resolveAsFileCollections();
 
     /**
      * Resolves the contents of this context as a sequence of atomic file trees.
      */
-    List<FileTree> resolveAsFileTrees();
+    List<FileTreeInternal> resolveAsFileTrees();
 }
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 93425f7..aede728 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
@@ -21,6 +21,7 @@ import org.gradle.api.file.FileVisitDetails;
 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.FileSystemSubset;
 import org.gradle.api.internal.file.pattern.PatternStep;
 import org.gradle.api.internal.file.pattern.PatternStepFactory;
 import org.gradle.api.specs.Spec;
@@ -130,4 +131,9 @@ public class SingleIncludePatternFileTree implements MinimalFileTree {
     public String getDisplayName() {
         return "directory '" + baseDir + "' include '" + includePattern + "'";
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        builder.add(baseDir, new PatternSet().include(includePattern).exclude(excludeSpec));
+    }
 }
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 71b0c2b..a52c8a0 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
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file.collections;
 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.FileSystemSubset;
 import org.gradle.internal.nativeintegration.filesystem.FileSystem;
 import org.gradle.internal.nativeintegration.services.FileSystems;
 
@@ -42,4 +43,10 @@ public class SingletonFileTree implements MinimalFileTree {
     public void visit(FileVisitor visitor) {
         visitor.visitFile(new DefaultFileVisitDetails(file, new RelativePath(true, file.getName()), new AtomicBoolean(), fileSystem, fileSystem));
     }
+
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        builder.add(file);
+    }
+
 }
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 378d252..3c451b5 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
@@ -21,44 +21,57 @@ 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.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.util.ConfigureUtil;
 
 import java.io.File;
 import java.net.URI;
 
-public class DefaultScriptHandler implements ScriptHandler {
+public class DefaultScriptHandler implements ScriptHandler, ScriptHandlerInternal {
+    private static final Logger LOGGER = Logging.getLogger(DefaultScriptHandler.class);
 
     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;
+    private final ClassLoaderScope classLoaderScope;
+    private Configuration classpathConfiguration;
 
     public DefaultScriptHandler(
             ScriptSource scriptSource, RepositoryHandler repositoryHandler,
             DependencyHandler dependencyHandler, ConfigurationContainer configContainer,
-            Factory<ClassLoader> classLoaderFactory
+            ClassLoaderScope classLoaderScope
     ) {
         this.repositoryHandler = repositoryHandler;
         this.dependencyHandler = dependencyHandler;
         this.scriptSource = scriptSource;
         this.configContainer = configContainer;
-        this.classLoaderFactory = classLoaderFactory;
-        classpathConfiguration = configContainer.create(CLASSPATH_CONFIGURATION);
+        this.classLoaderScope = classLoaderScope;
     }
 
     public void dependencies(Closure configureClosure) {
-        ConfigureUtil.configure(configureClosure, dependencyHandler);
+        ConfigureUtil.configure(configureClosure, getDependencies());
     }
 
-    protected Configuration getClasspathConfiguration() {
-        return classpathConfiguration;
+    @Override
+    public void addScriptClassPathDependency(Object notation) {
+        getDependencies().add(ScriptHandler.CLASSPATH_CONFIGURATION, notation);
+    }
+
+    @Override
+    public ClassPath getScriptClassPath() {
+        if (classpathConfiguration == null) {
+            return new DefaultClassPath();
+        }
+        return new DefaultClassPath(classpathConfiguration.getFiles());
     }
 
     public DependencyHandler getDependencies() {
+        defineConfiguration();
         return dependencyHandler;
     }
 
@@ -71,9 +84,17 @@ public class DefaultScriptHandler implements ScriptHandler {
     }
 
     public ConfigurationContainer getConfigurations() {
+        defineConfiguration();
         return configContainer;
     }
 
+    private void defineConfiguration() {
+        // Defer creation and resolution of configuration until required. Short-circuit when script does not require classpath
+        if (classpathConfiguration == null) {
+            classpathConfiguration = configContainer.create(CLASSPATH_CONFIGURATION);
+        }
+    }
+
     public File getSourceFile() {
         return scriptSource.getResource().getFile();
     }
@@ -83,7 +104,10 @@ public class DefaultScriptHandler implements ScriptHandler {
     }
 
     public ClassLoader getClassLoader() {
-        return classLoaderFactory.create();
+        if (!classLoaderScope.isLocked()) {
+            LOGGER.debug("Eager creation of script class loader for {}. This may result in performance issues.", scriptSource);
+        }
+        return classLoaderScope.getLocalClassLoader();
     }
 
 }
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 978ca17..793e551 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,7 +20,6 @@ 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;
@@ -48,16 +47,16 @@ public class DefaultScriptHandlerFactory implements ScriptHandlerFactory {
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
     }
 
-    public ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope) {
+    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope) {
         return create(scriptSource, classLoaderScope, new BasicDomainObjectContext());
     }
 
-    public ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope, DomainObjectContext context) {
+    public ScriptHandlerInternal 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();
-        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, new ScriptHandlerClassLoaderFactory(scriptSource, classLoaderScope));
+        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoaderScope);
     }
 
 }
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
deleted file mode 100644
index 70b508f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerClassLoaderFactory.java
+++ /dev/null
@@ -1,43 +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.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.getLocalClassLoader();
-    }
-
-}
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 3d5cb97..911f7a9 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,12 +16,11 @@
 
 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 {
-    ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope);
+    ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope);
 
-    ScriptHandler create(ScriptSource scriptSource, ClassLoaderScope classLoaderScope, DomainObjectContext context);
+    ScriptHandlerInternal 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
new file mode 100644
index 0000000..df61683
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.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.api.internal.initialization;
+
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.internal.classpath.ClassPath;
+
+public interface ScriptHandlerInternal extends ScriptHandler {
+    void addScriptClassPathDependency(Object notation);
+
+    ClassPath getScriptClassPath();
+}
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 ac55f29..6397866 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
@@ -52,6 +52,7 @@ 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.Actions;
 import org.gradle.internal.Factory;
 import org.gradle.internal.event.ListenerBroadcast;
 import org.gradle.internal.reflect.Instantiator;
@@ -186,47 +187,47 @@ public abstract class AbstractProject extends AbstractPluginAware implements Pro
     private void populateModelRegistry(ModelRegistry modelRegistry) {
         ModelPath taskFactoryPath = ModelPath.path("taskFactory");
         ModelCreator taskFactoryCreator = ModelCreators.bridgedInstance(ModelReference.of(taskFactoryPath, ITaskFactory.class), services.get(ITaskFactory.class))
-                .descriptor("Project.<init>.taskFactory")
-                .ephemeral(true)
-                .hidden(true)
-                .build();
+                                                       .descriptor("Project.<init>.taskFactory")
+                                                       .ephemeral(true)
+                                                       .hidden(true)
+                                                       .build();
 
         modelRegistry.createOrReplace(taskFactoryCreator);
 
         modelRegistry.createOrReplace(
-                ModelCreators.bridgedInstance(ModelReference.of("serviceRegistry", ServiceRegistry.class), services)
-                        .descriptor("Project.<init>.serviceRegistry()")
-                        .ephemeral(true)
-                        .hidden(true)
-                        .build()
+            ModelCreators.bridgedInstance(ModelReference.of("serviceRegistry", ServiceRegistry.class), services)
+                         .descriptor("Project.<init>.serviceRegistry()")
+                         .ephemeral(true)
+                         .hidden(true)
+                         .build()
         );
 
         modelRegistry.createOrReplace(
-                ModelCreators.unmanagedInstance(ModelReference.of("buildDir", File.class), new Factory<File>() {
-                    public File create() {
-                        return getBuildDir();
-                    }
-                })
-                        .descriptor("Project.<init>.buildDir()")
-                        .ephemeral(true)
-                        .hidden(true)
-                        .build()
+            ModelCreators.unmanagedInstance(ModelReference.of("buildDir", File.class), new Factory<File>() {
+                public File create() {
+                    return getBuildDir();
+                }
+            })
+                         .descriptor("Project.<init>.buildDir()")
+                         .ephemeral(true)
+                         .hidden(true)
+                         .build()
         );
 
         modelRegistry.createOrReplace(
-                ModelCreators.bridgedInstance(ModelReference.of("projectIdentifier", ProjectIdentifier.class), this)
-                        .descriptor("Project.<init>.projectIdentifier()")
-                        .ephemeral(true)
-                        .hidden(true)
-                        .build()
+            ModelCreators.bridgedInstance(ModelReference.of("projectIdentifier", ProjectIdentifier.class), this)
+                         .descriptor("Project.<init>.projectIdentifier()")
+                         .ephemeral(true)
+                         .hidden(true)
+                         .build()
         );
 
         modelRegistry.createOrReplace(
-                ModelCreators.bridgedInstance(ModelReference.of("extensions", ExtensionContainer.class), getExtensions())
-                        .descriptor("Project.<init>.extensions()")
-                        .ephemeral(true)
-                        .hidden(true)
-                        .build()
+            ModelCreators.bridgedInstance(ModelReference.of("extensions", ExtensionContainer.class), getExtensions())
+                         .descriptor("Project.<init>.extensions()")
+                         .ephemeral(true)
+                         .hidden(true)
+                         .build()
         );
     }
 
@@ -261,7 +262,7 @@ public abstract class AbstractProject extends AbstractPluginAware implements Pro
 
     public void setScript(groovy.lang.Script buildScript) {
         extensibleDynamicObject.addObject(new BeanDynamicObject(buildScript).withNoProperties().withNotImplementsMissing(),
-                ExtensibleDynamicObject.Location.BeforeConvention);
+            ExtensibleDynamicObject.Location.BeforeConvention);
     }
 
     public ScriptSource getBuildScriptSource() {
@@ -550,7 +551,7 @@ public abstract class AbstractProject extends AbstractPluginAware implements Pro
     private Project evaluationDependsOn(DefaultProject projectToEvaluate) {
         if (projectToEvaluate.getState().getExecuting()) {
             throw new CircularReferenceException(String.format("Circular referencing during evaluation for %s.",
-                    projectToEvaluate));
+                projectToEvaluate));
         }
         return projectToEvaluate.evaluate();
     }
@@ -762,7 +763,11 @@ public abstract class AbstractProject extends AbstractPluginAware implements Pro
     }
 
     public CopySpec copySpec(Action<? super CopySpec> action) {
-        return getFileOperations().copySpec(action);
+        return Actions.with(copySpec(), action);
+    }
+
+    public CopySpec copySpec() {
+        return getFileOperations().copySpec();
     }
 
     @Inject
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 e4855ac..0e1aa5b 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
@@ -17,7 +17,6 @@
 package org.gradle.api.internal.resources;
 
 import org.gradle.api.internal.file.FileOperations;
-import org.gradle.api.internal.file.MaybeCompressedFileResource;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
@@ -42,16 +41,7 @@ public class DefaultResourceHandler implements ResourceHandler {
         return new Bzip2Archiver(fileOperations.getFileResolver().resolveResource(path));
     }
 
-    //this method is not on the interface, at least for now
-    public ReadableResource maybeCompressed(Object tarPath) {
-        if (tarPath instanceof ReadableResource) {
-            return (ReadableResource) tarPath;
-        } else {
-            return new MaybeCompressedFileResource(fileOperations.getFileResolver().resolveResource(tarPath));
-        }
-    }
-
     public TextResourceFactory getText() {
         return textResourceFactory;
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/FileCollectionBackedArchiveTextResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/FileCollectionBackedArchiveTextResource.java
index 850cc8e..4e92bfd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/FileCollectionBackedArchiveTextResource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/FileCollectionBackedArchiveTextResource.java
@@ -16,14 +16,15 @@
 package org.gradle.api.internal.resources;
 
 import com.google.common.io.Files;
-
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.file.collections.LazilyInitializedFileTree;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.Cast;
 
 import java.io.File;
 import java.nio.charset.Charset;
@@ -35,15 +36,16 @@ public class FileCollectionBackedArchiveTextResource extends FileCollectionBacke
                                                    final String path, Charset charset) {
         super(tempFileProvider, new LazilyInitializedFileTree() {
             @Override
-            public FileTree createDelegate() {
+            public FileTreeInternal createDelegate() {
                 File archiveFile = fileCollection.getSingleFile();
                 String fileExtension = Files.getFileExtension(archiveFile.getName());
                 FileTree archiveContents = fileExtension.equals("jar") || fileExtension.equals("zip")
-                        ? fileOperations.zipTree(archiveFile) : fileOperations.tarTree(archiveFile);
+                    ? fileOperations.zipTree(archiveFile) : fileOperations.tarTree(archiveFile);
                 PatternSet patternSet = new PatternSet();
                 patternSet.include(path);
-                return archiveContents.matching(patternSet);
+                return Cast.cast(FileTreeInternal.class, archiveContents.matching(patternSet));
             }
+
             public TaskDependency getBuildDependencies() {
                 return fileCollection.getBuildDependencies();
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainer.java
new file mode 100644
index 0000000..af34b29
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public abstract class AddOnlyRuleAwarePolymorphicDomainObjectContainer<T> extends RuleAwarePolymorphicDomainObjectContainer<T> {
+    public AddOnlyRuleAwarePolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        super(type, instantiator);
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException("This collection does not support element removal.");
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException("This collection does not support element removal.");
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException("This collection does not support element removal.");
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> target) {
+        throw new UnsupportedOperationException("This collection does not support element removal.");
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return new RemovalPreventingDelegatingIterator<T>(super.iterator());
+    }
+
+    private static class RemovalPreventingDelegatingIterator<T> implements Iterator<T> {
+
+        private final Iterator<T> delegate;
+
+        public RemovalPreventingDelegatingIterator(Iterator<T> delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @Override
+        public T next() {
+            return delegate.next();
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("This iterator does not support removal.");
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistry.java
new file mode 100644
index 0000000..277f61f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistry.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Map;
+
+public class DefaultRuleAwareNamedDomainObjectFactoryRegistry<T> implements RuleAwareNamedDomainObjectFactoryRegistry<T> {
+
+    private final Map<Class<? extends T>, Optional<ModelRuleDescriptor>> creators = Maps.newHashMap();
+    private final NamedDomainObjectFactoryRegistry<T> delegate;
+
+    public DefaultRuleAwareNamedDomainObjectFactoryRegistry(NamedDomainObjectFactoryRegistry<T> delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+        registerFactory(type, factory, null);
+    }
+
+    @Override
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
+        checkCanRegister(type, descriptor);
+        delegate.registerFactory(type, factory);
+    }
+
+    private void checkCanRegister(Class<? extends T> type, ModelRuleDescriptor descriptor) {
+        Optional<ModelRuleDescriptor> creator = creators.get(type);
+        if (creator != null) {
+            StringBuilder builder = new StringBuilder("Cannot register a factory for type ")
+                .append(type.getSimpleName())
+                .append(" because a factory for this type was already registered");
+
+            if (creator.isPresent()) {
+                builder.append(" by ");
+                creator.get().describeTo(builder);
+            }
+            builder.append(".");
+            throw new GradleException(builder.toString());
+        }
+        creators.put(type, Optional.fromNullable(descriptor));
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiator.java
new file mode 100644
index 0000000..b731062
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiator.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.PolymorphicNamedEntityInstantiator;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Set;
+
+public class DefaultRuleAwarePolymorphicNamedEntityInstantiator<T> implements RuleAwarePolymorphicNamedEntityInstantiator<T> {
+
+    private final PolymorphicNamedEntityInstantiator<T> instantiator;
+    private final RuleAwareNamedDomainObjectFactoryRegistry<T> registry;
+
+    public DefaultRuleAwarePolymorphicNamedEntityInstantiator(PolymorphicNamedEntityInstantiator<T> instantiator) {
+        this(instantiator, new DefaultRuleAwareNamedDomainObjectFactoryRegistry<T>(instantiator));
+    }
+
+    public DefaultRuleAwarePolymorphicNamedEntityInstantiator(PolymorphicNamedEntityInstantiator<T> instantiator, RuleAwareNamedDomainObjectFactoryRegistry<T> registry) {
+        this.instantiator = instantiator;
+        this.registry = registry;
+    }
+
+    @Override
+    public <S extends T> S create(String name, Class<S> type) {
+        return instantiator.create(name, type);
+    }
+
+    @Override
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
+        registry.registerFactory(type, factory, descriptor);
+    }
+
+    @Override
+    public Set<? extends Class<? extends T>> getCreatableTypes() {
+        return instantiator.getCreatableTypes();
+    }
+
+    @Override
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+        registry.registerFactory(type, factory);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/ModelMapCreators.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/ModelMapCreators.java
new file mode 100644
index 0000000..5bab321
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/ModelMapCreators.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.api.internal.DefaultPolymorphicNamedEntityInstantiator;
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
+import org.gradle.model.ModelMap;
+import org.gradle.model.collection.internal.PolymorphicModelMapProjection;
+import org.gradle.model.internal.core.*;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+
+public class ModelMapCreators {
+
+    public static <T, C extends ModelMap<T>> ModelCreator specialized(ModelPath path,
+                                                                      Class<T> typeClass,
+                                                                      Class<C> containerClass,
+                                                                      Class<? extends C> viewClass,
+                                                                      ModelReference<? extends InstanceFactory<? super T, String>> factoryReference,
+                                                                      ModelRuleDescriptor descriptor) {
+
+        ChildNodeCreatorStrategy<T> childFactory = NodeBackedModelMap.createUsingFactory(factoryReference);
+
+        ModelType<C> containerType = ModelType.of(containerClass);
+        ModelType<T> modelType = ModelType.of(typeClass);
+        return ModelCreators.of(ModelReference.of(path, containerType), Factories.<C>constantNull())
+            .descriptor(descriptor)
+            .withProjection(new SpecializedModelMapProjection<C, T>(containerType, modelType, viewClass, childFactory))
+            .withProjection(PolymorphicModelMapProjection.of(modelType, childFactory))
+            .build();
+    }
+
+    public static <T> ModelCreators.Builder of(ModelPath path, final Class<T> typeClass) {
+
+        final ModelType<RuleAwarePolymorphicNamedEntityInstantiator<T>> instantiatorType = instantiatorType(typeClass);
+
+        ModelType<T> modelType = ModelType.of(typeClass);
+        return ModelCreators.of(
+            ModelReference.of(path, instantiatorType),
+            new Factory<RuleAwarePolymorphicNamedEntityInstantiator<T>>() {
+                @Override
+                public RuleAwarePolymorphicNamedEntityInstantiator<T> create() {
+                    return new DefaultRuleAwarePolymorphicNamedEntityInstantiator<T>(
+                        new DefaultPolymorphicNamedEntityInstantiator<T>(typeClass, "this collection")
+                    );
+                }
+            }
+        )
+            .withProjection(PolymorphicModelMapProjection.of(modelType, NodeBackedModelMap.createUsingParentNode(modelType)))
+            .withProjection(UnmanagedModelProjection.of(instantiatorType));
+    }
+
+    public static <T> ModelType<RuleAwarePolymorphicNamedEntityInstantiator<T>> instantiatorType(Class<T> typeClass) {
+        return new ModelType.Builder<RuleAwarePolymorphicNamedEntityInstantiator<T>>() {
+        }.where(new ModelType.Parameter<T>() {
+        }, ModelType.of(typeClass)).build();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/NamedDomainObjectFactoryRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/NamedDomainObjectFactoryRegistry.java
new file mode 100644
index 0000000..84feb98
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/NamedDomainObjectFactoryRegistry.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.api.NamedDomainObjectFactory;
+
+public interface NamedDomainObjectFactoryRegistry<T> {
+
+    <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwareNamedDomainObjectFactoryRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwareNamedDomainObjectFactoryRegistry.java
new file mode 100644
index 0000000..000a1a6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwareNamedDomainObjectFactoryRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+public interface RuleAwareNamedDomainObjectFactoryRegistry<T> extends NamedDomainObjectFactoryRegistry<T> {
+
+    <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainer.java
new file mode 100644
index 0000000..db9dce7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainer.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.rules;
+
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+public abstract class RuleAwarePolymorphicDomainObjectContainer<T> extends DefaultPolymorphicDomainObjectContainer<T> implements RuleAwareNamedDomainObjectFactoryRegistry<T> {
+    private final DefaultRuleAwarePolymorphicNamedEntityInstantiator<T> ruleAwareInstantiator;
+
+    public RuleAwarePolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        super(type, instantiator);
+        this.ruleAwareInstantiator = new DefaultRuleAwarePolymorphicNamedEntityInstantiator<T>(namedEntityInstantiator);
+    }
+
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
+        ruleAwareInstantiator.registerFactory(type, factory, descriptor);
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicNamedEntityInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicNamedEntityInstantiator.java
new file mode 100644
index 0000000..11436d4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicNamedEntityInstantiator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules;
+
+import org.gradle.api.internal.PolymorphicNamedEntityInstantiator;
+import org.gradle.model.internal.core.NamedEntityInstantiator;
+
+public interface RuleAwarePolymorphicNamedEntityInstantiator<T> extends NamedEntityInstantiator<T>, PolymorphicNamedEntityInstantiator<T>, RuleAwareNamedDomainObjectFactoryRegistry<T> {
+}
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 57e7359..81cdc1d 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
@@ -25,7 +25,6 @@ import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.initialization.ProjectAccessListener;
-import org.gradle.internal.BiAction;
 import org.gradle.internal.Transformers;
 import org.gradle.internal.graph.CachingDirectedGraphWalker;
 import org.gradle.internal.graph.DirectedGraph;
@@ -41,16 +40,14 @@ import java.util.*;
 
 public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements TaskContainerInternal {
     private final MutableModelNode modelNode;
-    private final ModelReference<NamedEntityInstantiator<Task>> instantiatorReference;
     private final ITaskFactory taskFactory;
     private final ProjectAccessListener projectAccessListener;
     private final Set<String> placeholders = Sets.newHashSet();
     private final NamedEntityInstantiator<Task> instantiator;
 
-    public DefaultTaskContainer(MutableModelNode modelNode, ModelReference<NamedEntityInstantiator<Task>> instantiatorReference, ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory, ProjectAccessListener projectAccessListener) {
+    public DefaultTaskContainer(MutableModelNode modelNode, ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory, ProjectAccessListener projectAccessListener) {
         super(Task.class, instantiator, project);
         this.modelNode = modelNode;
-        this.instantiatorReference = instantiatorReference;
         this.taskFactory = taskFactory;
         this.projectAccessListener = projectAccessListener;
         this.instantiator = new TaskInstantiator(taskFactory);
@@ -75,7 +72,7 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
                 remove(existing);
             } else {
                 throw new InvalidUserDataException(String.format(
-                        "Cannot add %s as a task with that name already exists.", task));
+                    "Cannot add %s as a task with that name already exists.", task));
             }
         }
 
@@ -236,37 +233,19 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         if (!modelNode.hasLink(placeholderName)) {
             final ModelType<T> taskModelType = ModelType.of(taskType);
             ModelPath path = MODEL_PATH.child(placeholderName);
-            addPlaceholderModelLink(placeholderName, taskType, configure, taskModelType, path, instantiatorReference, modelNode);
+            modelNode.addLink(
+                ModelCreators
+                    .of(path, new TaskCreator<T>(placeholderName, taskType, configure, taskModelType))
+                    .withProjection(new UnmanagedModelProjection<T>(taskModelType, true, true))
+                    .descriptor(new SimpleModelRuleDescriptor("tasks.addPlaceholderAction(" + placeholderName + ")"))
+                    .build()
+            );
         }
         if (findByNameWithoutRules(placeholderName) == null) {
             placeholders.add(placeholderName);
         }
     }
 
-    private static <T extends TaskInternal> void addPlaceholderModelLink(final String placeholderName, final Class<T> taskType, final Action<? super T> configure, final ModelType<T> taskModelType, ModelPath path, final ModelReference<NamedEntityInstantiator<Task>> instantiatorReference, final MutableModelNode modelNode) {
-        modelNode.addLink(
-                ModelCreators
-                        .of(ModelReference.of(path), new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                            @Override
-                            public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
-                                NamedEntityInstantiator<Task> instantiator = ModelViews.getInstance(inputs.get(0), instantiatorReference);
-                                final T task = instantiator.create(placeholderName, taskType);
-                                configure.execute(task);
-                                DeprecationLogger.whileDisabled(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        modelNode.getPrivateData(ModelType.of(TaskContainerInternal.class)).add(task);
-                                    }
-                                });
-                                mutableModelNode.setPrivateData(taskModelType, task);
-                            }
-                        })
-                        .inputs(instantiatorReference)
-                        .withProjection(new UnmanagedModelProjection<T>(taskModelType, true, true))
-                        .descriptor(new SimpleModelRuleDescriptor("tasks.addPlaceholderAction(" + placeholderName + ")"))
-                        .build()
-        );
-    }
 
     public <U extends Task> NamedDomainObjectContainer<U> containerWithType(Class<U> type) {
         throw new UnsupportedOperationException();
@@ -291,4 +270,32 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
             return type.cast(taskFactory.create(name, type.asSubclass(TaskInternal.class)));
         }
     }
-}
\ No newline at end of file
+
+    private static class TaskCreator<T extends TaskInternal> implements Action<MutableModelNode> {
+        private final String placeholderName;
+        private final Class<T> taskType;
+        private final Action<? super T> configure;
+        private final ModelType<T> taskModelType;
+
+        public TaskCreator(String placeholderName, Class<T> taskType, Action<? super T> configure, ModelType<T> taskModelType) {
+            this.placeholderName = placeholderName;
+            this.taskType = taskType;
+            this.configure = configure;
+            this.taskModelType = taskModelType;
+        }
+
+        @Override
+        public void execute(final MutableModelNode mutableModelNode) {
+            DeprecationLogger.whileDisabled(new Runnable() {
+                @Override
+                public void run() {
+                    DefaultTaskContainer taskContainer = mutableModelNode.getParent().getPrivateData(ModelType.of(DefaultTaskContainer.class));
+                    T task = taskContainer.taskFactory.create(placeholderName, taskType);
+                    configure.execute(task);
+                    taskContainer.add(task);
+                    mutableModelNode.setPrivateData(taskModelType, task);
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
index 22b1673..0cdea67 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
@@ -21,13 +21,13 @@ import org.gradle.api.Transformer;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.internal.BiAction;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.model.collection.internal.BridgedCollections;
-import org.gradle.model.internal.core.ModelNode;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.core.MutableModelNode;
-import org.gradle.model.internal.core.NamedEntityInstantiator;
+import org.gradle.model.collection.internal.ModelMapModelProjection;
+import org.gradle.model.internal.core.*;
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
 import org.gradle.model.internal.registry.ModelRegistry;
 import org.gradle.model.internal.type.ModelType;
 
@@ -46,25 +46,32 @@ public class DefaultTaskContainerFactory implements Factory<TaskContainerInterna
         this.projectAccessListener = projectAccessListener;
     }
 
-    ModelType<Task> taskModelType = ModelType.of(Task.class);
-
     public TaskContainerInternal create() {
-        BridgedCollections.staticTypes(
-                modelRegistry,
-                TaskContainerInternal.MODEL_PATH,
-                TaskContainerInternal.MODEL_TYPE,
-                taskModelType,
-                ModelType.of(TaskContainer.class),
-                new Transformer<TaskContainerInternal, MutableModelNode>() {
+        ModelReference<DefaultTaskContainer> containerReference = ModelReference.of(TaskContainerInternal.MODEL_PATH, DefaultTaskContainer.class);
+
+        ModelCreators.Builder creatorBuilder = BridgedCollections.creator(
+            containerReference,
+            new Transformer<DefaultTaskContainer, MutableModelNode>() {
+                @Override
+                public DefaultTaskContainer transform(MutableModelNode mutableModelNode) {
+                    return instantiator.newInstance(DefaultTaskContainer.class, mutableModelNode, project, instantiator, taskFactory, projectAccessListener);
+                }
+            },
+            new Task.Namer(),
+            "Project.<init>.tasks()",
+            new Namer()
+        );
+
+        modelRegistry.createOrReplace(
+            creatorBuilder
+                .withProjection(ModelMapModelProjection.unmanaged(Task.class, NodeBackedModelMap.createUsingParentNode(new Transformer<NamedEntityInstantiator<Task>, MutableModelNode>() {
                     @Override
-                    public TaskContainerInternal transform(MutableModelNode mutableModelNode) {
-                        ModelReference<NamedEntityInstantiator<Task>> instantiatorReference = BridgedCollections.instantiatorReference(TaskContainerInternal.MODEL_PATH, TaskContainerInternal.TASK_MODEL_TYPE);
-                        return instantiator.newInstance(DefaultTaskContainer.class, mutableModelNode, instantiatorReference, project, instantiator, taskFactory, projectAccessListener);
+                    public NamedEntityInstantiator<Task> transform(MutableModelNode modelNode) {
+                        return modelNode.getPrivateData(ModelType.of(DefaultTaskContainer.class)).getEntityInstantiator();
                     }
-                },
-                new Task.Namer(),
-                "Project.<init>.tasks()",
-                new Namer()
+                })))
+                .withProjection(UnmanagedModelProjection.of(TaskContainer.class))
+                .build()
         );
 
         ModelNode modelNode = modelRegistry.atStateOrLater(TaskContainerInternal.MODEL_PATH, ModelNode.State.Created);
@@ -74,6 +81,16 @@ public class DefaultTaskContainerFactory implements Factory<TaskContainerInterna
 
         // TODO LD use something more stable than a cast here
         MutableModelNode mutableModelNode = (MutableModelNode) modelNode;
+
+        // Add tasks created through rules to the actual task container
+        mutableModelNode.applyToAllLinks(ModelActionRole.Mutate, DirectNodeNoInputsModelAction.of(ModelReference.of(Task.class), new SimpleModelRuleDescriptor("copyToTaskContainer"), new BiAction<MutableModelNode, Task>() {
+            @Override
+            public void execute(MutableModelNode modelNode, Task task) {
+                TaskContainerInternal taskContainer = modelNode.getParent().getPrivateData(TaskContainerInternal.MODEL_TYPE);
+                taskContainer.add(task);
+            }
+        }));
+
         return mutableModelNode.getPrivateData(TaskContainerInternal.MODEL_TYPE);
     }
 
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 f737a3c..e0ea861 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
@@ -54,4 +54,4 @@ public interface TaskContainerInternal extends TaskContainer, TaskResolver, Poly
     void discoverTasks();
 
     void maybeRealizeTask(String name);
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
index 3f4d2a0..05754d5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
@@ -28,6 +28,7 @@ public class TaskStateInternal implements TaskState {
     private String description;
     private String skippedMessage;
     private boolean skipped;
+    private boolean upToDate;
 
     public TaskStateInternal(String description) {
         this.description = description;
@@ -79,8 +80,9 @@ public class TaskStateInternal implements TaskState {
      */
     public void upToDate() {
         skipped("UP-TO-DATE");
+        upToDate = true;
     }
-    
+
     public boolean getExecuting() {
         return executing;
     }
@@ -113,4 +115,8 @@ public class TaskStateInternal implements TaskState {
     public String getSkipMessage() {
         return skippedMessage;
     }
+
+    public boolean getUpToDate() {
+        return 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 b6ecff7..73349de 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
@@ -15,29 +15,39 @@
  */
 package org.gradle.api.internal.tasks.execution;
 
+import org.gradle.api.execution.internal.TaskInputsListener;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.file.FileCollectionInternal;
 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;
+import org.gradle.internal.Cast;
 
 /**
  * A {@link TaskExecuter} which skips tasks whose source file collection is empty.
  */
 public class SkipEmptySourceFilesTaskExecuter implements TaskExecuter {
     private static final Logger LOGGER = Logging.getLogger(SkipEmptySourceFilesTaskExecuter.class);
+    private final TaskInputsListener taskInputsListener;
     private final TaskExecuter executer;
 
-    public SkipEmptySourceFilesTaskExecuter(TaskExecuter executer) {
+    public SkipEmptySourceFilesTaskExecuter(TaskInputsListener taskInputsListener, TaskExecuter executer) {
+        this.taskInputsListener = taskInputsListener;
         this.executer = executer;
     }
 
     public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
-        if (task.getInputs().getHasSourceFiles() && task.getInputs().getSourceFiles().isEmpty()) {
+        FileCollection sourceFiles = task.getInputs().getSourceFiles();
+        if (task.getInputs().getHasSourceFiles() && sourceFiles.isEmpty()) {
             LOGGER.info("Skipping {} as it has no source files.", task);
             state.upToDate();
+            taskInputsListener.onExecute(task, Cast.cast(FileCollectionInternal.class, sourceFiles));
             return;
+        } else {
+            taskInputsListener.onExecute(task, Cast.cast(FileCollectionInternal.class, task.getInputs().getFiles()));
         }
         executer.execute(task, state, context);
     }
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
deleted file mode 100644
index 9031780..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
+++ /dev/null
@@ -1,25 +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
-
-/**
- * An {@code AntBuilderAware} represents an object which can add itself to Ant tasks, using an
- * {@link org.gradle.api.AntBuilder}.
- */
-interface AntBuilderAware {
-    def addToAntBuilder(node, String childNodeName)
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.java
new file mode 100644
index 0000000..e931d32
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * An {@code AntBuilderAware} represents an object which can add itself to Ant tasks, using an {@link org.gradle.api.AntBuilder}.
+ */
+public interface AntBuilderAware {
+    Object addToAntBuilder(Object node, String childNodeName);
+}
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 be4145e..bbc2a20 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
@@ -114,7 +114,7 @@ public class GradleBuild extends ConventionTask {
     void build() {
         GradleLauncher launcher = gradleLauncherFactory.newInstance(getStartParameter());
         try {
-            launcher.run().rethrowFailure();
+            launcher.run();
         } finally {
             launcher.stop();
         }
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 597cc09..0c65eb6 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
@@ -43,7 +43,8 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
     @InputFiles
     @SkipWhenEmpty
     public FileTree getSource() {
-        FileTree src = getProject().files(new ArrayList<Object>(this.source)).getAsFileTree();
+        ArrayList<Object> copy = new ArrayList<Object>(this.source);
+        FileTree src = getProject().files(copy).getAsFileTree();
         return src == null ? getProject().files().getAsFileTree() : src.matching(patternSet);
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskState.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskState.java
index 64b160a..1b919da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskState.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskState.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.tasks;
 
+import org.gradle.api.Incubating;
 import org.gradle.api.Nullable;
 
 /**
@@ -66,4 +67,14 @@ public interface TaskState {
      */
     @Nullable
     String getSkipMessage();
+
+    /**
+     * Returns true if the execution of this task was skipped because the task was up-to-date.
+     *
+     * @return true if this task has been considered up-to-date
+     *
+     * @since 2.5
+     */
+    @Incubating
+    boolean getUpToDate();
 }
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
index dc072fa..a724912 100644
--- 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
@@ -37,6 +37,9 @@ import org.gradle.api.NonExtensible;
  *
  *      @TaskAction
  *      void execute(IncrementalTaskInputs inputs) {
+ *          if (!inputs.incremental)
+ *              project.delete(outputDir.listFiles())
+ *
  *          inputs.outOfDate { change ->
  *              def targetFile = project.file("$outputDir/${change.file.name}")
  *              targetFile.text = change.file.text.reverse()
@@ -118,4 +121,4 @@ public interface IncrementalTaskInputs {
      * @throws IllegalStateException if invoked prior to {@link #outOfDate}, or if invoked more than once.
      */
     void removed(Action<? super InputFileDetails> removedAction);
-}
\ 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 7ea7507..a69cf4c 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,8 @@ import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.RelativePathSpec;
 import org.gradle.api.internal.file.pattern.PatternMatcherFactory;
-import org.gradle.api.specs.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.AntBuilderAware;
 import org.gradle.api.tasks.util.internal.PatternSetAntBuilderDelegate;
 import org.gradle.internal.typeconversion.NotationParser;
@@ -42,11 +43,12 @@ import java.util.Set;
  */
 public class PatternSet implements AntBuilderAware, PatternFilterable {
 
+    private static final NotationParser<Object, String> PARSER = NotationParserBuilder.toType(String.class).fromCharSequence().toComposite();
+
     private final Set<String> includes = Sets.newLinkedHashSet();
     private final Set<String> excludes = Sets.newLinkedHashSet();
     private final Set<Spec<FileTreeElement>> includeSpecs = Sets.newLinkedHashSet();
     private final Set<Spec<FileTreeElement>> excludeSpecs = Sets.newLinkedHashSet();
-    private static final NotationParser<Object, String> PARSER = NotationParserBuilder.toType(String.class).fromCharSequence().toComposite();
 
     boolean caseSensitive = true;
 
@@ -64,10 +66,16 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
         if (caseSensitive != that.caseSensitive) {
             return false;
         }
-        if (!excludes.equals(that.excludes)) {
+        if (excludeSpecs != null ? !excludeSpecs.equals(that.excludeSpecs) : that.excludeSpecs != null) {
             return false;
         }
-        if (!includes.equals(that.includes)) {
+        if (excludes != null ? !excludes.equals(that.excludes) : that.excludes != null) {
+            return false;
+        }
+        if (includeSpecs != null ? !includeSpecs.equals(that.includeSpecs) : that.includeSpecs != null) {
+            return false;
+        }
+        if (includes != null ? !includes.equals(that.includes) : that.includes != null) {
             return false;
         }
 
@@ -76,20 +84,40 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
 
     @Override
     public int hashCode() {
-        int result = includes.hashCode();
-        result = 31 * result + excludes.hashCode();
+        int result = includes != null ? includes.hashCode() : 0;
+        result = 31 * result + (excludes != null ? excludes.hashCode() : 0);
+        result = 31 * result + (includeSpecs != null ? includeSpecs.hashCode() : 0);
+        result = 31 * result + (excludeSpecs != null ? excludeSpecs.hashCode() : 0);
         result = 31 * result + (caseSensitive ? 1 : 0);
         return result;
     }
 
     public PatternSet copyFrom(PatternFilterable sourcePattern) {
-        setIncludes(sourcePattern.getIncludes());
-        setExcludes(sourcePattern.getExcludes());
-        PatternSet other = (PatternSet) sourcePattern;
+        return doCopyFrom((PatternSet) sourcePattern);
+    }
+
+    protected PatternSet doCopyFrom(PatternSet from) {
+        includes.clear();
+        excludes.clear();
         includeSpecs.clear();
-        includeSpecs.addAll(other.includeSpecs);
         excludeSpecs.clear();
-        excludeSpecs.addAll(other.excludeSpecs);
+        caseSensitive = from.caseSensitive;
+
+        if (from instanceof IntersectionPatternSet) {
+            PatternSet other = ((IntersectionPatternSet) from).other;
+            PatternSet otherCopy = new PatternSet().copyFrom(other);
+            PatternSet intersectCopy = new IntersectionPatternSet(otherCopy);
+            intersectCopy.includes.addAll(from.includes);
+            intersectCopy.excludes.addAll(from.excludes);
+            intersectCopy.includeSpecs.addAll(from.includeSpecs);
+            intersectCopy.excludeSpecs.addAll(from.excludeSpecs);
+            includeSpecs.add(intersectCopy.getAsSpec());
+        } else {
+            includes.addAll(from.includes);
+            excludes.addAll(from.excludes);
+            includeSpecs.addAll(from.includeSpecs);
+            excludeSpecs.addAll(from.excludeSpecs);
+        }
 
         return this;
     }
@@ -161,6 +189,7 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
         this.includes.clear();
         return include(includes);
     }
+
     public PatternSet include(String... includes) {
         Collections.addAll(this.includes, includes);
         return this;
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 6b66328..5306604 100755
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
@@ -21,6 +21,7 @@ import org.gradle.api.internal.DocumentationRegistry;
 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.internal.plugins.PluginAwareInternal;
 import org.gradle.api.internal.plugins.PluginManagerInternal;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -70,7 +71,7 @@ public class DefaultScriptPluginFactory implements ScriptPluginFactory {
     }
 
     public ScriptPlugin create(ScriptSource scriptSource, ScriptHandler scriptHandler, ClassLoaderScope targetScope, ClassLoaderScope baseScope, String classpathClosureName, Class<? extends BasicScript> scriptClass, boolean ownerScript) {
-        return new ScriptPluginImpl(scriptSource, scriptHandler, targetScope, baseScope, classpathClosureName, scriptClass, ownerScript);
+        return new ScriptPluginImpl(scriptSource, (ScriptHandlerInternal) scriptHandler, targetScope, baseScope, classpathClosureName, scriptClass, ownerScript);
     }
 
     private class ScriptPluginImpl implements ScriptPlugin {
@@ -79,10 +80,10 @@ public class DefaultScriptPluginFactory implements ScriptPluginFactory {
         private final ClassLoaderScope baseScope;
         private final String classpathClosureName;
         private final Class<? extends BasicScript> scriptType;
-        private final ScriptHandler scriptHandler;
+        private final ScriptHandlerInternal scriptHandler;
         private final boolean ownerScript;
 
-        public ScriptPluginImpl(ScriptSource scriptSource, ScriptHandler scriptHandler, ClassLoaderScope targetScope, ClassLoaderScope baseScope, String classpathClosureName, Class<? extends BasicScript> scriptType, boolean ownerScript) {
+        public ScriptPluginImpl(ScriptSource scriptSource, ScriptHandlerInternal scriptHandler, ClassLoaderScope targetScope, ClassLoaderScope baseScope, String classpathClosureName, Class<? extends BasicScript> scriptType, boolean ownerScript) {
             this.scriptSource = scriptSource;
             this.targetScope = targetScope;
             this.baseScope = baseScope;
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/CancellableOperationManager.java b/subprojects/core/src/main/groovy/org/gradle/execution/CancellableOperationManager.java
new file mode 100644
index 0000000..595151e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/CancellableOperationManager.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Action;
+import org.gradle.initialization.BuildCancellationToken;
+
+public interface CancellableOperationManager {
+
+    /**
+     * Executes the operation, while consuming System.in, watching for closure or EOT.
+     */
+    void monitorInput(Action<? super BuildCancellationToken> operation);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultCancellableOperationManager.java b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultCancellableOperationManager.java
new file mode 100644
index 0000000..929696c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultCancellableOperationManager.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Action;
+import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.DisconnectableInputStream;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+public class DefaultCancellableOperationManager implements CancellableOperationManager {
+
+    private static final int EOF = -1;
+    private static final int KEY_CODE_CTRL_D = 4;
+
+    private final ExecutorService executorService;
+    private final DisconnectableInputStream input;
+    private final BuildCancellationToken cancellationToken;
+
+    public DefaultCancellableOperationManager(ExecutorService executorService, DisconnectableInputStream input, BuildCancellationToken cancellationToken) {
+        this.executorService = executorService;
+        this.input = input;
+        this.cancellationToken = cancellationToken;
+    }
+
+    @Override
+    public void monitorInput(final Action<? super BuildCancellationToken> operation) {
+        Future<?> handle = null;
+        try {
+            handle = executorService.submit(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        while (!Thread.currentThread().isInterrupted()) {
+                            int c = input.read();
+                            if (c == KEY_CODE_CTRL_D || c == EOF) {
+                                cancellationToken.cancel();
+                                break;
+                            }
+                        }
+                    } catch (IOException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+            });
+            operation.execute(cancellationToken);
+        } finally {
+            if (handle != null) {
+                handle.cancel(true);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/PassThruCancellableOperationManager.java b/subprojects/core/src/main/groovy/org/gradle/execution/PassThruCancellableOperationManager.java
new file mode 100644
index 0000000..1017c72
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/PassThruCancellableOperationManager.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Action;
+import org.gradle.initialization.BuildCancellationToken;
+
+public class PassThruCancellableOperationManager implements CancellableOperationManager {
+    private final BuildCancellationToken cancellationToken;
+
+    public PassThruCancellableOperationManager(BuildCancellationToken cancellationToken) {
+        this.cancellationToken = cancellationToken;
+    }
+
+    @Override
+    public void monitorInput(Action<? super BuildCancellationToken> operation) {
+        operation.execute(cancellationToken);
+    }
+}
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
index fbc86c3..eeef00e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/AbstractTaskPlanExecutor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/AbstractTaskPlanExecutor.java
@@ -16,7 +16,7 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.Action;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -25,19 +25,18 @@ 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);
+    protected Runnable taskWorker(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
+        return new TaskExecutorWorker(taskExecutionPlan, taskWorker);
     }
 
-    private class TaskExecutorWorker implements Runnable {
+    private static class TaskExecutorWorker implements Runnable {
         private final TaskExecutionPlan taskExecutionPlan;
-        private final TaskExecutionListener taskListener;
+        private final Action<? super TaskInternal> taskWorker;
 
-        private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+        private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
             this.taskExecutionPlan = taskExecutionPlan;
-            this.taskListener = taskListener;
+            this.taskWorker = taskWorker;
         }
 
         public void run() {
@@ -60,28 +59,12 @@ abstract class AbstractTaskPlanExecutor implements TaskPlanExecutor {
 
         protected void processTask(TaskInfo taskInfo) {
             try {
-                executeTask(taskInfo);
+                taskWorker.execute(taskInfo.getTask());
             } 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/DefaultTaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
index 81d696e..0ae28ad 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
@@ -17,16 +17,29 @@
 package org.gradle.execution.taskgraph;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionGraphListener;
 import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.execution.internal.InternalTaskExecutionListener;
+import org.gradle.api.execution.internal.TaskOperationInternal;
+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.execution.DefaultTaskExecutionContext;
 import org.gradle.api.specs.Spec;
 import org.gradle.execution.TaskFailureHandler;
 import org.gradle.execution.TaskGraphExecuter;
 import org.gradle.initialization.BuildCancellationToken;
-import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
+import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
 import org.gradle.internal.event.ListenerBroadcast;
 import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.progress.BuildOperationExecutor;
+import org.gradle.internal.progress.OperationIdGenerator;
+import org.gradle.internal.progress.OperationResult;
+import org.gradle.internal.progress.OperationStartEvent;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.util.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,15 +56,25 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
     }
 
     private final TaskPlanExecutor taskPlanExecutor;
+    // This currently needs to be lazy, as it uses state that is not available when the graph is created
+    private final Factory<? extends TaskExecuter> taskExecuter;
+    private final TimeProvider timeProvider;
     private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
     private final ListenerBroadcast<TaskExecutionListener> taskListeners;
+    private final ListenerBroadcast<InternalTaskExecutionListener> internalTaskListeners;
     private final DefaultTaskExecutionPlan taskExecutionPlan;
+    private final BuildOperationExecutor buildOperationExecutor;
+    private final Object eventNotificationLock = new Object();
     private TaskGraphState taskGraphState = TaskGraphState.EMPTY;
 
-    public DefaultTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor, BuildCancellationToken cancellationToken) {
+    public DefaultTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor, Factory<? extends TaskExecuter> taskExecuter, BuildCancellationToken cancellationToken, TimeProvider timeProvider, BuildOperationExecutor buildOperationExecutor) {
         this.taskPlanExecutor = taskPlanExecutor;
+        this.taskExecuter = taskExecuter;
+        this.timeProvider = timeProvider;
+        this.buildOperationExecutor = buildOperationExecutor;
         graphListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionGraphListener.class);
         taskListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionListener.class);
+        internalTaskListeners = listenerManager.createAnonymousBroadcaster(InternalTaskExecutionListener.class);
         taskExecutionPlan = new DefaultTaskExecutionPlan(cancellationToken);
     }
 
@@ -85,7 +108,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
 
         graphListeners.getSource().graphPopulated(this);
         try {
-            taskPlanExecutor.process(taskExecutionPlan, taskListeners.getSource());
+            taskPlanExecutor.process(taskExecutionPlan, new EventFiringTaskWorker(taskExecuter.create()));
             logger.debug("Timing: Executing the DAG took " + clock.getTime());
         } finally {
             taskExecutionPlan.clear();
@@ -153,4 +176,45 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
             case POPULATED:
         }
     }
+
+    /**
+     * This action will set the start and end times on the internal task state, and will make sure
+     * that when a task is started, the public listeners are executed after the internal listeners
+     * are executed and when a task is finished, the public listeners are executed before the internal
+     * listeners are executed. Basically the internal listeners embrace the public listeners.
+     */
+    private class EventFiringTaskWorker implements Action<TaskInternal> {
+        private final TaskExecuter taskExecuter;
+
+        public EventFiringTaskWorker(TaskExecuter taskExecuter) {
+            this.taskExecuter = taskExecuter;
+        }
+
+        @Override
+        public void execute(TaskInternal task) {
+            Object id = OperationIdGenerator.generateId(task);
+            Object parentId = buildOperationExecutor.getCurrentOperationId();
+            TaskOperationInternal taskOperation = new TaskOperationInternal(id, parentId, task);
+            TaskStateInternal state = task.getState();
+            long startTime = timeProvider.getCurrentTime();
+            // TODO - move serialization to ListenerManager contract
+            synchronized (eventNotificationLock) {
+                internalTaskListeners.getSource().beforeExecute(taskOperation, new OperationStartEvent(startTime));
+            }
+            try {
+                synchronized (eventNotificationLock) {
+                    taskListeners.getSource().beforeExecute(task);
+                }
+                taskExecuter.execute(task, task.getState(), new DefaultTaskExecutionContext());
+                synchronized (eventNotificationLock) {
+                    taskListeners.getSource().afterExecute(task, state);
+                }
+            } finally {
+                synchronized (eventNotificationLock) {
+                    long endTime = timeProvider.getCurrentTime();
+                    internalTaskListeners.getSource().afterExecute(taskOperation, new OperationResult(startTime, endTime, task.getState().getFailure()));
+                }
+            }
+        }
+    }
 }
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 6bf6e12..a13790c 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
@@ -16,11 +16,13 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.Action;
+import org.gradle.api.internal.TaskInternal;
 
 class DefaultTaskPlanExecutor extends AbstractTaskPlanExecutor {
-    public void process(final TaskExecutionPlan taskExecutionPlan, final TaskExecutionListener taskListener) {
-        taskWorker(taskExecutionPlan, taskListener).run();
+    @Override
+    public void process(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
+        taskWorker(taskExecutionPlan, taskWorker).run();
         taskExecutionPlan.awaitCompletion();
     }
 }
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 c8559d7..ab07443 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
@@ -16,7 +16,8 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.Action;
+import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.concurrent.ExecutorFactory;
@@ -38,22 +39,23 @@ class ParallelTaskPlanExecutor extends AbstractTaskPlanExecutor {
         this.executorCount = numberOfParallelExecutors;
     }
 
-    public void process(final TaskExecutionPlan taskExecutionPlan, final TaskExecutionListener taskListener) {
+    @Override
+    public void process(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
         StoppableExecutor executor = executorFactory.create("Task worker");
         try {
-            startAdditionalWorkers(taskExecutionPlan, taskListener, executor);
-            taskWorker(taskExecutionPlan, taskListener).run();
+            startAdditionalWorkers(taskExecutionPlan, taskWorker, executor);
+            taskWorker(taskExecutionPlan, taskWorker).run();
             taskExecutionPlan.awaitCompletion();
         } finally {
             executor.stop();
         }
     }
 
-    private void startAdditionalWorkers(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener, Executor executor) {
+    private void startAdditionalWorkers(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker, Executor executor) {
         LOGGER.info("Using {} parallel executor threads", executorCount);
 
         for (int i = 1; i < executorCount; i++) {
-            Runnable worker = taskWorker(taskExecutionPlan, taskListener);
+            Runnable worker = taskWorker(taskExecutionPlan, taskWorker);
             executor.execute(worker);
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java
index 12a0e1e..6e87459 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java
@@ -16,8 +16,15 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.Action;
+import org.gradle.api.internal.TaskInternal;
 
+/**
+ * Will be merged with {@link org.gradle.internal.operations.BuildOperationProcessor}
+ */
 public interface TaskPlanExecutor {
-    void process(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener);
+    /**
+     * Supplied worker must be thread-safe.
+     */
+    void process(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker);
 }
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 16b5eb9..b8d6467 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
@@ -40,6 +40,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.Actions;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.process.ExecResult;
@@ -60,6 +61,7 @@ public abstract class DefaultScript extends BasicScript {
 
     public ServiceRegistry __scriptServices;
 
+    @Override
     public void init(final Object target, ServiceRegistry services) {
         super.init(target, services);
         this.__scriptServices = services;
@@ -77,6 +79,7 @@ public abstract class DefaultScript extends BasicScript {
         processOperations = (ProcessOperations) fileOperations;
     }
 
+    @Override
     public FileResolver getFileResolver() {
         return fileOperations.getFileResolver();
     }
@@ -84,130 +87,159 @@ public abstract class DefaultScript extends BasicScript {
     private DefaultObjectConfigurationAction createObjectConfigurationAction() {
         ClassLoaderScope classLoaderScope = __scriptServices.get(ClassLoaderScope.class);
         return new DefaultObjectConfigurationAction(
-                getFileResolver(),
-                __scriptServices.get(ScriptPluginFactory.class),
-                __scriptServices.get(ScriptHandlerFactory.class),
-                classLoaderScope,
-                getScriptTarget()
+            getFileResolver(),
+            __scriptServices.get(ScriptPluginFactory.class),
+            __scriptServices.get(ScriptHandlerFactory.class),
+            classLoaderScope,
+            getScriptTarget()
         );
     }
 
+    @Override
     public void apply(Closure closure) {
         DefaultObjectConfigurationAction action = createObjectConfigurationAction();
         ConfigureUtil.configure(closure, action);
         action.execute();
     }
 
+    @Override
     public void apply(Map options) {
         DefaultObjectConfigurationAction action = createObjectConfigurationAction();
         ConfigureUtil.configureByMap(options, action);
         action.execute();
     }
 
+    @Override
     public ScriptHandler getBuildscript() {
         return __scriptServices.get(ScriptHandler.class);
     }
 
+    @Override
     public void buildscript(Closure configureClosure) {
         ConfigureUtil.configure(configureClosure, getBuildscript());
     }
 
+    @Override
     public File file(Object path) {
         return fileOperations.file(path);
     }
 
+    @Override
     public File file(Object path, PathValidation validation) {
         return fileOperations.file(path, validation);
     }
 
+    @Override
     public URI uri(Object path) {
         return fileOperations.uri(path);
     }
 
+    @Override
     public ConfigurableFileCollection files(Object... paths) {
         return fileOperations.files(paths);
     }
 
+    @Override
     public ConfigurableFileCollection files(Object paths, Closure configureClosure) {
         return ConfigureUtil.configure(configureClosure, fileOperations.files(paths));
     }
 
+    @Override
     public String relativePath(Object path) {
         return fileOperations.relativePath(path);
     }
 
+    @Override
     public ConfigurableFileTree fileTree(Object baseDir) {
         return fileOperations.fileTree(baseDir);
     }
 
+    @Override
     public ConfigurableFileTree fileTree(Map<String, ?> args) {
         return fileOperations.fileTree(args);
     }
 
+    @Override
     public ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure) {
         return ConfigureUtil.configure(configureClosure, fileOperations.fileTree(baseDir));
     }
 
+    @Override
     public FileTree zipTree(Object zipPath) {
         return fileOperations.zipTree(zipPath);
     }
 
+    @Override
     public FileTree tarTree(Object tarPath) {
         return fileOperations.tarTree(tarPath);
     }
 
+    @Override
     public ResourceHandler getResources() {
         return fileOperations.getResources();
     }
 
+    @Override
     public WorkResult copy(Closure closure) {
         return copy(new ClosureBackedAction<CopySpec>(closure));
     }
 
+    @Override
     public WorkResult copy(Action<? super CopySpec> action) {
         return fileOperations.copy(action);
     }
 
+    @Override
     public WorkResult sync(Action<? super CopySpec> action) {
         return fileOperations.sync(action);
     }
 
+    @Override
     public CopySpec copySpec(Closure closure) {
-        return fileOperations.copySpec(new ClosureBackedAction<CopySpec>(closure));
+        return Actions.with(copySpec(), new ClosureBackedAction<CopySpec>(closure));
     }
 
-    public CopySpec copySpec(Action<? super CopySpec> action) {
-        return fileOperations.copySpec(action);
+    @Override
+    public CopySpec copySpec() {
+        return fileOperations.copySpec();
     }
 
+    @Override
     public File mkdir(Object path) {
         return fileOperations.mkdir(path);
     }
 
+    @Override
     public boolean delete(Object... paths) {
         return fileOperations.delete(paths);
     }
 
+    @Override
     public ExecResult javaexec(Closure closure) {
         return processOperations.javaexec(new ClosureBackedAction<JavaExecSpec>(closure));
     }
 
+    @Override
     public ExecResult javaexec(Action<? super JavaExecSpec> action) {
         return processOperations.javaexec(action);
     }
 
+    @Override
     public ExecResult exec(Closure closure) {
         return processOperations.exec(new ClosureBackedAction<ExecSpec>(closure));
     }
 
+    @Override
     public ExecResult exec(Action<? super ExecSpec> action) {
         return processOperations.exec(action);
     }
 
+    @Override
     public LoggingManager getLogging() {
         return loggingManager;
     }
 
+    @Override
     public Logger getLogger() {
         return LOGGER;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildCancellationToken.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildCancellationToken.java
index 16d0425..d8fdc0a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildCancellationToken.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildCancellationToken.java
@@ -20,8 +20,11 @@ package org.gradle.initialization;
  * Propagates notification that the build should be cancelled.
  */
 public interface BuildCancellationToken {
+
     boolean isCancellationRequested();
 
+    void cancel();
+
     /**
      * @return current state of cancellation request before callback was added.
      */
@@ -33,4 +36,5 @@ public interface BuildCancellationToken {
      * @param cancellationHandler removed callback.
      */
     void removeCallback(Runnable cancellationHandler);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildRequestContext.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildRequestContext.java
index 0e73950..e156a58 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildRequestContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildRequestContext.java
@@ -16,6 +16,8 @@
 
 package org.gradle.initialization;
 
+import org.gradle.api.logging.StandardOutputListener;
+
 /**
  * Provides access to services provided by build requester.
  */
@@ -29,4 +31,14 @@ public interface BuildRequestContext extends BuildRequestMetaData {
      * Returns an event consumer that will forward events to the build requester.
      */
     BuildEventConsumer getEventConsumer();
+
+    /**
+     * Returns an output listener that will receive standard output from the build.
+     */
+    StandardOutputListener getOutputListener();
+
+    /**
+     * Returns an output listener that will receive standard error from the build.
+     */
+    StandardOutputListener getErrorListener();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildCancellationToken.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildCancellationToken.java
index fb98fcc..acc8d27 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildCancellationToken.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildCancellationToken.java
@@ -23,6 +23,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 public class DefaultBuildCancellationToken implements BuildCancellationToken {
+
     private final Object lock = new Object();
     private boolean cancelled;
     private List<Runnable> callbacks = new LinkedList<Runnable>();
@@ -53,7 +54,7 @@ public class DefaultBuildCancellationToken implements BuildCancellationToken {
         }
     }
 
-    public void doCancel() {
+    public void cancel() {
         List<Runnable> toCall = new ArrayList<Runnable>();
         synchronized (lock) {
             if (cancelled) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestContext.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestContext.java
index 14eecf2..ccf5b45 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestContext.java
@@ -16,17 +16,26 @@
 
 package org.gradle.initialization;
 
+import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.util.Clock;
 
 public class DefaultBuildRequestContext implements BuildRequestContext {
     private final BuildCancellationToken token;
     private final BuildEventConsumer buildEventConsumer;
     private final BuildRequestMetaData metaData;
+    private final StandardOutputListener outputListener;
+    private final StandardOutputListener errorListener;
 
-    public DefaultBuildRequestContext(BuildRequestMetaData metaData, BuildCancellationToken token, BuildEventConsumer buildEventConsumer) {
+    public DefaultBuildRequestContext(BuildRequestMetaData metaData, BuildCancellationToken token, BuildEventConsumer buildEventConsumer, StandardOutputListener outputListener, StandardOutputListener errorListener) {
         this.metaData = metaData;
         this.token = token;
         this.buildEventConsumer = buildEventConsumer;
+        this.outputListener = outputListener;
+        this.errorListener = errorListener;
+    }
+
+    public DefaultBuildRequestContext(BuildRequestMetaData metaData, BuildCancellationToken token, BuildEventConsumer buildEventConsumer) {
+        this(metaData, token, buildEventConsumer, new NoOpListener(), new NoOpListener());
     }
 
     @Override
@@ -48,4 +57,20 @@ public class DefaultBuildRequestContext implements BuildRequestContext {
     public Clock getBuildTimeClock() {
         return metaData.getBuildTimeClock();
     }
+
+    @Override
+    public StandardOutputListener getErrorListener() {
+        return errorListener;
+    }
+
+    @Override
+    public StandardOutputListener getOutputListener() {
+        return outputListener;
+    }
+
+    private static class NoOpListener implements StandardOutputListener {
+        @Override
+        public void onOutput(CharSequence output) {
+        }
+    }
 }
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 60a4d7f..d59f278 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -50,6 +50,9 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
 
     private static final String CONFIGURE_ON_DEMAND = "configure-on-demand";
 
+    private static final String CONTINUOUS = "continuous";
+    private static final String CONTINUOUS_SHORT_FLAG = "t";
+
     private final CommandLineConverter<LoggingConfiguration> loggingConfigurationCommandLineConverter = new LoggingCommandLineConverter();
     private final SystemPropertiesCommandLineConverter systemPropertiesCommandLineConverter = new SystemPropertiesCommandLineConverter();
     private final ProjectPropertiesCommandLineConverter projectPropertiesCommandLineConverter = new ProjectPropertiesCommandLineConverter();
@@ -84,6 +87,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
                 deprecated("Please use --parallel, optionally in conjunction with --max-workers.").incubating();
         parser.option(MAX_WORKERS).hasArgument().hasDescription("Configure the number of concurrent workers Gradle is allowed to use.").incubating();
         parser.option(CONFIGURE_ON_DEMAND).hasDescription("Only relevant projects are configured in this build run. This means faster build for large multi-project builds.").incubating();
+        parser.option(CONTINUOUS, CONTINUOUS_SHORT_FLAG).hasDescription("Enables continuous build. Gradle does not exit and will re-execute tasks when task file inputs change.").incubating();
         parser.allowOneOf(MAX_WORKERS, PARALLEL_THREADS);
     }
 
@@ -193,6 +197,10 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
             startParameter.setConfigureOnDemand(true);
         }
 
+        if (options.hasOption(CONTINUOUS)) {
+            startParameter.setContinuous(true);
+        }
+
         return startParameter;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
index 3d6635f..251d324 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -23,7 +23,11 @@ import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
+import org.gradle.internal.Factory;
 import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.progress.BuildOperationExecutor;
+import org.gradle.internal.progress.BuildOperationType;
+import org.gradle.internal.progress.OperationIdGenerator;
 import org.gradle.logging.LoggingManagerInternal;
 
 import java.io.Closeable;
@@ -34,16 +38,17 @@ public class DefaultGradleLauncher extends GradleLauncher {
     }
 
     private final GradleInternal gradle;
+    private final InitScriptHandler initScriptHandler;
     private final SettingsHandler settingsHandler;
     private final BuildLoader buildLoader;
     private final BuildConfigurer buildConfigurer;
     private final ExceptionAnalyser exceptionAnalyser;
-    private final BuildListener buildListener;
-    private final InitScriptHandler initScriptHandler;
     private final LoggingManagerInternal loggingManager;
+    private final BuildListener buildListener;
     private final ModelConfigurationListener modelConfigurationListener;
     private final TasksCompletionListener tasksCompletionListener;
     private final BuildCompletionListener buildCompletionListener;
+    private final BuildOperationExecutor buildOperationExecutor;
     private final BuildExecuter buildExecuter;
     private final Closeable buildServices;
 
@@ -51,11 +56,11 @@ public class DefaultGradleLauncher extends GradleLauncher {
      * Creates a new instance.
      */
     public DefaultGradleLauncher(GradleInternal gradle, InitScriptHandler initScriptHandler, SettingsHandler settingsHandler,
-                                 BuildLoader buildLoader, BuildConfigurer buildConfigurer, BuildListener buildListener,
-                                 ExceptionAnalyser exceptionAnalyser, LoggingManagerInternal loggingManager,
+                                 BuildLoader buildLoader, BuildConfigurer buildConfigurer, ExceptionAnalyser exceptionAnalyser,
+                                 LoggingManagerInternal loggingManager, BuildListener buildListener,
                                  ModelConfigurationListener modelConfigurationListener, TasksCompletionListener tasksCompletionListener,
-                                 BuildExecuter buildExecuter, BuildCompletionListener buildCompletionListener,
-                                 Closeable buildServices) {
+                                 BuildCompletionListener buildCompletionListener, BuildOperationExecutor operationExecutor,
+                                 BuildExecuter buildExecuter, Closeable buildServices) {
         this.gradle = gradle;
         this.initScriptHandler = initScriptHandler;
         this.settingsHandler = settingsHandler;
@@ -66,6 +71,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
         this.loggingManager = loggingManager;
         this.modelConfigurationListener = modelConfigurationListener;
         this.tasksCompletionListener = tasksCompletionListener;
+        this.buildOperationExecutor = operationExecutor;
         this.buildExecuter = buildExecuter;
         this.buildCompletionListener = buildCompletionListener;
         this.buildServices = buildServices;
@@ -75,83 +81,114 @@ public class DefaultGradleLauncher extends GradleLauncher {
         return gradle;
     }
 
-    /**
-     * <p>Executes the build for this GradleLauncher instance and returns the result. Note that when the build fails,
-     * the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
-     *
-     * @return The result. Never returns null.
-     */
     @Override
     public BuildResult run() {
         return doBuild(Stage.Build);
     }
 
-    /**
-     * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
-     * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object.
-     *
-     * @return A BuildResult object. Never returns null.
-     */
     @Override
     public BuildResult getBuildAnalysis() {
         return doBuild(Stage.Configure);
     }
 
-    private BuildResult doBuild(Stage upTo) {
+    private BuildResult doBuild(final Stage upTo) {
         loggingManager.start();
-        buildListener.buildStarted(gradle);
 
-        Throwable failure = null;
-        try {
-            doBuildStages(upTo);
-        } catch (Throwable t) {
-            failure = exceptionAnalyser.transform(t);
-        }
-        BuildResult buildResult = new BuildResult(gradle, failure);
-        buildListener.buildFinished(buildResult);
-
-        return buildResult;
+        return runRootBuildOperation(BuildOperationType.RUNNING_BUILD, new Factory<BuildResult>() {
+            @Override
+            public BuildResult create() {
+                buildListener.buildStarted(gradle);
+
+                Throwable failure = null;
+                try {
+                    doBuildStages(upTo);
+                } catch (Throwable t) {
+                    failure = exceptionAnalyser.transform(t);
+                }
+                BuildResult buildResult = new BuildResult(gradle, failure);
+                buildListener.buildFinished(buildResult);
+                if (failure != null) {
+                    throw new ReportedException(failure);
+                }
+
+                return buildResult;
+            }
+        });
     }
 
     private void doBuildStages(Stage upTo) {
         // Evaluate init scripts
-        initScriptHandler.executeScripts(gradle);
+        runBuildOperation(BuildOperationType.EVALUATING_INIT_SCRIPTS, new Runnable() {
+            @Override
+            public void run() {
+                initScriptHandler.executeScripts(gradle);
+            }
+        });
 
         // Evaluate settings script
-        SettingsInternal settings = settingsHandler.findAndLoadSettings(gradle);
-        buildListener.settingsEvaluated(settings);
-
-        // Load build
-        buildLoader.load(settings.getRootProject(), settings.getDefaultProject(), gradle, settings.getRootClassLoaderScope());
-        buildListener.projectsLoaded(gradle);
+        runBuildOperation(BuildOperationType.EVALUATING_SETTINGS, new Runnable() {
+            @Override
+            public void run() {
+                SettingsInternal settings = settingsHandler.findAndLoadSettings(gradle);
+                buildListener.settingsEvaluated(settings);
+                buildLoader.load(settings.getRootProject(), settings.getDefaultProject(), gradle, settings.getRootClassLoaderScope());
+                buildListener.projectsLoaded(gradle);
+            }
+        });
 
         // Configure build
-        buildConfigurer.configure(gradle);
+        runBuildOperation(BuildOperationType.CONFIGURING_BUILD, new Runnable() {
+            @Override
+            public void run() {
+                buildConfigurer.configure(gradle);
 
-        if (!gradle.getStartParameter().isConfigureOnDemand()) {
-            buildListener.projectsEvaluated(gradle);
-        }
+                if (!gradle.getStartParameter().isConfigureOnDemand()) {
+                    buildListener.projectsEvaluated(gradle);
+                }
+
+                modelConfigurationListener.onConfigure(gradle);
+            }
+        });
 
-        modelConfigurationListener.onConfigure(gradle);
 
         if (upTo == Stage.Configure) {
             return;
         }
 
         // Populate task graph
-        buildExecuter.select(gradle);
+        runBuildOperation(BuildOperationType.POPULATING_TASK_GRAPH, new Runnable() {
+            @Override
+            public void run() {
+                buildExecuter.select(gradle);
 
-        if (gradle.getStartParameter().isConfigureOnDemand()) {
-            buildListener.projectsEvaluated(gradle);
-        }
+                if (gradle.getStartParameter().isConfigureOnDemand()) {
+                    buildListener.projectsEvaluated(gradle);
+                }
+            }
+        });
 
         // Execute build
-        buildExecuter.execute();
-        tasksCompletionListener.onTasksFinished(gradle);
+        runBuildOperation(BuildOperationType.EXECUTING_TASKS, new Runnable() {
+            @Override
+            public void run() {
+                buildExecuter.execute();
+                tasksCompletionListener.onTasksFinished(gradle);
+            }
+        });
 
         assert upTo == Stage.Build;
     }
 
+    private <T> T runRootBuildOperation(BuildOperationType operationType, Factory<T> factory) {
+        Object id = OperationIdGenerator.generateId(gradle);
+        return buildOperationExecutor.run(id, operationType, factory);
+    }
+
+    private void runBuildOperation(BuildOperationType operationType, Runnable action) {
+        Object id = OperationIdGenerator.generateId(operationType, gradle);
+        buildOperationExecutor.run(id, operationType, action);
+    }
+
     /**
      * <p>Adds a listener to this build instance. The listener is notified of events which occur during the
      * execution of the build. See {@link org.gradle.api.invocation.Gradle#addListener(Object)} for supported listener
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 5f5e727..8cca908 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -30,9 +30,7 @@ import org.gradle.initialization.buildsrc.BuildSourceBuilder;
 import org.gradle.initialization.layout.BuildLayoutFactory;
 import org.gradle.internal.event.ListenerManager;
 import org.gradle.internal.featurelifecycle.ScriptUsageLocationReporter;
-import org.gradle.internal.progress.BuildProgressFilter;
-import org.gradle.internal.progress.BuildProgressLogger;
-import org.gradle.internal.progress.LoggerProvider;
+import org.gradle.internal.progress.*;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.scopes.BuildScopeServices;
@@ -54,7 +52,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         sharedServices = globalServices;
         tracker = new NestedBuildTracker();
 
-        // Register default loggers 
+        // Register default loggers
         ListenerManager listenerManager = sharedServices.get(ListenerManager.class);
         buildProgressLogger = new BuildProgressLogger(sharedServices.get(ProgressLoggerFactory.class));
         listenerManager.addListener(new BuildProgressFilter(buildProgressLogger));
@@ -80,7 +78,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
             buildEventConsumer = services.get(BuildEventConsumer.class);
         } else {
             requestMetaData = new DefaultBuildRequestMetaData(System.currentTimeMillis());
-            cancellationToken = new FixedBuildCancellationToken();
+            cancellationToken = new DefaultBuildCancellationToken();
             buildEventConsumer = new NoOpBuildEventConsumer();
         }
         return doNewInstance(startParameter, cancellationToken, requestMetaData, buildEventConsumer);
@@ -124,27 +122,28 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
 
         GradleInternal gradle = serviceRegistry.get(Instantiator.class).newInstance(DefaultGradle.class, tracker.getCurrentBuild(), startParameter, serviceRegistry.get(ServiceRegistryFactory.class));
         return new DefaultGradleLauncher(
-                gradle,
-                serviceRegistry.get(InitScriptHandler.class),
-                new SettingsHandler(
-                        new DefaultSettingsFinder(
-                                new BuildLayoutFactory()),
-                        serviceRegistry.get(SettingsProcessor.class),
-                        new BuildSourceBuilder(
-                                this,
-                                serviceRegistry.get(ClassLoaderScopeRegistry.class).getCoreAndPluginsScope(),
-                                serviceRegistry.get(CacheRepository.class))
-                ),
-                serviceRegistry.get(BuildLoader.class),
-                serviceRegistry.get(BuildConfigurer.class),
-                gradle.getBuildListenerBroadcaster(),
-                serviceRegistry.get(ExceptionAnalyser.class),
-                loggingManager,
-                listenerManager.getBroadcaster(ModelConfigurationListener.class),
-                listenerManager.getBroadcaster(TasksCompletionListener.class),
-                gradle.getServices().get(BuildExecuter.class),
-                listenerManager.getBroadcaster(BuildCompletionListener.class),
-                serviceRegistry
+            gradle,
+            serviceRegistry.get(InitScriptHandler.class),
+            new SettingsHandler(
+                new DefaultSettingsFinder(
+                    new BuildLayoutFactory()),
+                serviceRegistry.get(SettingsProcessor.class),
+                new BuildSourceBuilder(
+                    this,
+                    serviceRegistry.get(ClassLoaderScopeRegistry.class).getCoreAndPluginsScope(),
+                    serviceRegistry.get(CacheRepository.class))
+            ),
+            serviceRegistry.get(BuildLoader.class),
+            serviceRegistry.get(BuildConfigurer.class),
+            serviceRegistry.get(ExceptionAnalyser.class),
+            loggingManager,
+            gradle.getBuildListenerBroadcaster(),
+            listenerManager.getBroadcaster(ModelConfigurationListener.class),
+            listenerManager.getBroadcaster(TasksCompletionListener.class),
+            listenerManager.getBroadcaster(BuildCompletionListener.class),
+            serviceRegistry.get(BuildOperationExecutor.class),
+            gradle.getServices().get(BuildExecuter.class),
+            serviceRegistry
         );
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/FixedBuildCancellationToken.java b/subprojects/core/src/main/groovy/org/gradle/initialization/FixedBuildCancellationToken.java
deleted file mode 100644
index 732728a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/FixedBuildCancellationToken.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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;
-
-/**
- * Implementation used for toolingApi requests from consumer without cancellation support.
- */
-public class FixedBuildCancellationToken implements BuildCancellationToken {
-    public boolean isCancellationRequested() {
-        return false;
-    }
-
-    public boolean addCallback(Runnable cancellationHandler) {
-        return false;
-    }
-
-    public void removeCallback(Runnable cancellationHandler) {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncher.java
index a763abf..96d4e40 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncher.java
@@ -26,20 +26,21 @@ import org.gradle.internal.concurrent.Stoppable;
 public abstract class GradleLauncher implements Stoppable {
 
     /**
-     * <p>Executes the build for this {@code GradleLauncher} instance and returns the result. Note that when the build
-     * fails, the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
+     * <p>Executes the build for this {@code GradleLauncher} instance and returns the result.</p>
      *
      * @return The result. Never returns null.
+     * @throws ReportedException On build failure. The failure will have been logged.
      */
-    public abstract BuildResult run();
+    public abstract BuildResult run() throws ReportedException;
 
     /**
      * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
      * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object.
      *
      * @return The result. Never returns null.
+     * @throws ReportedException On build failure. The failure will have been logged.
      */
-    public abstract BuildResult getBuildAnalysis();
+    public abstract BuildResult getBuildAnalysis() throws ReportedException;
 
     /**
      * <p>Adds a listener to this build instance. The listener is notified of events which occur during the execution of
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ReportedException.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ReportedException.java
new file mode 100644
index 0000000..88a254a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ReportedException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization;
+
+/**
+ * Wraps an exception which has already been logged, and should not be logged again.
+ */
+public class ReportedException extends RuntimeException {
+    public ReportedException(Throwable throwable) {
+        super(throwable);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java
index 2c6494b..f49ba8c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/buildsrc/BuildSrcUpdateFactory.java
@@ -16,11 +16,11 @@
 
 package org.gradle.initialization.buildsrc;
 
-import org.gradle.initialization.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.initialization.GradleLauncher;
 import org.gradle.internal.Factory;
 import org.gradle.internal.classpath.DefaultClassPath;
 
@@ -46,7 +46,7 @@ public class BuildSrcUpdateFactory implements Factory<DefaultClassPath> {
 
         BuildSrcBuildListenerFactory.Listener listener = listenerFactory.create(rebuild);
         gradleLauncher.addListener(listener);
-        gradleLauncher.run().rethrowFailure();
+        gradleLauncher.run();
 
         Collection<File> classpath = listener.getRuntimeClasspath();
         LOGGER.debug("Gradle source classpath is: {}", classpath);
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiter.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiter.java
new file mode 100644
index 0000000..e5ef7a9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiter.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultFileSystemChangeWaiter implements FileSystemChangeWaiter {
+    private final ExecutorFactory executorFactory;
+    private final FileWatcherFactory fileWatcherFactory;
+    private final long quietPeriodMillis;
+
+    public DefaultFileSystemChangeWaiter(ExecutorFactory executorFactory, FileWatcherFactory fileWatcherFactory) {
+        this(executorFactory, fileWatcherFactory, 250L);
+    }
+
+    public DefaultFileSystemChangeWaiter(ExecutorFactory executorFactory, FileWatcherFactory fileWatcherFactory, long quietPeriodMillis) {
+        this.executorFactory = executorFactory;
+        this.fileWatcherFactory = fileWatcherFactory;
+        this.quietPeriodMillis = quietPeriodMillis;
+    }
+
+    @Override
+    public void wait(FileSystemSubset taskFileSystemInputs, final BuildCancellationToken cancellationToken, Runnable notifier) {
+        if (cancellationToken.isCancellationRequested()) {
+            return;
+        }
+
+        final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
+        final StoppableExecutor executorService = executorFactory.create("continuous build - wait");
+
+        final Lock lock = new ReentrantLock();
+        final Condition condition = lock.newCondition();
+        final AtomicLong lastChangeAt = new AtomicLong(0);
+
+        Runnable cancellationHandler = new Runnable() {
+            @Override
+            public void run() {
+                signal(lock, condition);
+            }
+        };
+
+
+        FileWatcher watcher = fileWatcherFactory.watch(
+            taskFileSystemInputs,
+            new Action<Throwable>() {
+                @Override
+                public void execute(Throwable throwable) {
+                    error.set(throwable);
+                    signal(lock, condition);
+                }
+            },
+            new FileWatcherListener() {
+                @Override
+                public void onChange(final FileWatcher watcher, FileWatcherEvent event) {
+                    if (!(event.getType() == FileWatcherEvent.Type.MODIFY && event.getFile().isDirectory())) {
+                        signal(lock, condition, new Runnable() {
+                            @Override
+                            public void run() {
+                                lastChangeAt.set(System.currentTimeMillis());
+                            }
+                        });
+                    }
+                }
+            }
+        );
+
+        try {
+            cancellationToken.addCallback(cancellationHandler);
+            notifier.run();
+            lock.lock();
+            try {
+                long lastChangeAtValue = lastChangeAt.get();
+                while (!cancellationToken.isCancellationRequested() && error.get()==null && (lastChangeAtValue == 0 || System.currentTimeMillis() - lastChangeAtValue < quietPeriodMillis)) {
+                    condition.await(quietPeriodMillis, TimeUnit.MILLISECONDS);
+                    lastChangeAtValue = lastChangeAt.get();
+                }
+            } finally {
+                lock.unlock();
+            }
+            Throwable throwable = error.get();
+            if (throwable != null) {
+                throw throwable;
+            }
+        } catch (Throwable e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            cancellationToken.removeCallback(cancellationHandler);
+            CompositeStoppable.stoppable(watcher, executorService).stop();
+        }
+    }
+
+    private void signal(Lock lock, Condition condition, Runnable runnable) {
+        lock.lock();
+        try {
+            runnable.run();
+            condition.signal();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void signal(Lock lock, Condition condition) {
+        signal(lock, condition, new Runnable() {
+            @Override
+            public void run() {
+
+            }
+        });
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactory.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactory.java
new file mode 100644
index 0000000..973fbe0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import org.gradle.api.Action;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.internal.Cast;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.Stoppable;
+import org.gradle.internal.reflect.DirectInstantiator;
+
+import java.util.concurrent.ExecutorService;
+
+public class DefaultFileWatcherFactory implements FileWatcherFactory, Stoppable {
+    private final ExecutorService executor;
+    private final JavaVersion javaVersion;
+    private final ClassLoader classLoader;
+
+    private FileWatcherFactory fileWatcherFactory;
+
+    public DefaultFileWatcherFactory(ExecutorFactory executorFactory) {
+        this(JavaVersion.current(), DefaultFileWatcherFactory.class.getClassLoader(), executorFactory);
+    }
+
+    DefaultFileWatcherFactory(JavaVersion javaVersion, ClassLoader classLoader, ExecutorFactory executorFactory) {
+        this.javaVersion = javaVersion;
+        this.classLoader = classLoader;
+        this.executor = executorFactory.create("filewatcher");
+    }
+
+    protected FileWatcherFactory createFileWatcherFactory() {
+        if (javaVersion.isJava7Compatible()) {
+            try {
+                Class clazz = classLoader.loadClass("org.gradle.internal.filewatch.jdk7.Jdk7FileWatcherFactory");
+                return Cast.uncheckedCast(DirectInstantiator.instantiate(clazz, executor));
+            } catch (ClassNotFoundException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        } else {
+            throw new UnsupportedOperationException("File watching requires Java 7 or later.");
+        }
+    }
+
+    @Override
+    public void stop() {
+        executor.shutdown();
+    }
+
+    @Override
+    public FileWatcher watch(FileSystemSubset systemSubset, Action<? super Throwable> onError, FileWatcherListener listener) {
+        if (fileWatcherFactory == null) {
+            fileWatcherFactory = createFileWatcherFactory();
+        }
+        return fileWatcherFactory.watch(systemSubset, onError, listener);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileSystemChangeWaiter.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileSystemChangeWaiter.java
new file mode 100644
index 0000000..4aa445b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileSystemChangeWaiter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.initialization.BuildCancellationToken;
+
+public interface FileSystemChangeWaiter {
+    void wait(FileSystemSubset taskFileSystemInputs, BuildCancellationToken cancellationToken, Runnable notifier);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcher.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcher.java
new file mode 100644
index 0000000..285a759
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcher.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.concurrent.Stoppable;
+
+ at ThreadSafe
+public interface FileWatcher extends Stoppable {
+
+    /**
+     * Is the watcher watching files.
+     * <p>
+     * This may return true while the watcher is in the process of stopping.
+     * No events will be emitted by the watcher when this is false.
+     * <p>
+     * As the watcher can be stopped asynchronously,
+     * the watcher may be stopped after it has emitted an event but before the event is received.
+     *
+     * @return is the watcher running.
+     */
+    boolean isRunning();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherEvent.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherEvent.java
new file mode 100644
index 0000000..02f259a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+public class FileWatcherEvent {
+
+    public enum Type {
+        CREATE,
+        MODIFY,
+        DELETE,
+        UNDEFINED // something happened, but we don't know what
+    }
+
+    private final Type type;
+    private final File file;
+
+    private FileWatcherEvent(Type type, File file) {
+        this.type = type;
+        this.file = file;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    @Nullable // null if type == UNDEFINED
+    public File getFile() {
+        return file;
+    }
+
+    @Override
+    public String toString() {
+        return "FileWatcherEvent{type=" + type + ", file=" + file + '}';
+    }
+
+    public static FileWatcherEvent create(File file) {
+        return new FileWatcherEvent(Type.CREATE, file);
+    }
+
+    public static FileWatcherEvent modify(File file) {
+        return new FileWatcherEvent(Type.MODIFY, file);
+    }
+
+    public static FileWatcherEvent delete(File file) {
+        return new FileWatcherEvent(Type.DELETE, file);
+    }
+
+    public static FileWatcherEvent undefined() {
+        return new FileWatcherEvent(Type.UNDEFINED, null);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherFactory.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherFactory.java
new file mode 100644
index 0000000..e74ef04
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.FileSystemSubset;
+
+public interface FileWatcherFactory {
+
+    /**
+     * Starts watching for changes to the given subset of the file system.
+     * <p>
+     * Listeners should not rely on the events being an entirely accurate journal of file system events.
+     * Implementations may emit duplicate events and chronological ordering is not guaranteed.
+     * <p>
+     * It can be assumed that all changes to the file system subset that occur <b>after</b> the return of this method will produce events.
+     * <p>
+     * No events will be emitted after that watcher has been stopped.
+     *
+     * @param systemSubset the parts of the file system to watch
+     * @param onError what to do if an error occurs while watching
+     * @param listener the receiver of events
+     * @return a, stoppable, handle to the watcher
+     */
+    FileWatcher watch(FileSystemSubset systemSubset, Action<? super Throwable> onError, FileWatcherListener listener);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherListener.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherListener.java
new file mode 100644
index 0000000..0acc87d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/FileWatcherListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch;
+
+public interface FileWatcherListener {
+    void onChange(FileWatcher watcher, FileWatcherEvent event);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/Jdk7FileWatcherFactory.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/Jdk7FileWatcherFactory.java
new file mode 100644
index 0000000..1f3cb59
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/Jdk7FileWatcherFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch.jdk7;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.filewatch.FileWatcher;
+import org.gradle.internal.filewatch.FileWatcherFactory;
+import org.gradle.internal.filewatch.FileWatcherListener;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.WatchService;
+import java.util.concurrent.ExecutorService;
+
+public class Jdk7FileWatcherFactory implements FileWatcherFactory {
+
+    private final ListeningExecutorService executor;
+
+    public Jdk7FileWatcherFactory(ExecutorService executor) {
+        this.executor = MoreExecutors.listeningDecorator(executor);
+    }
+
+    @Override
+    public FileWatcher watch(FileSystemSubset fileSystemSubset, Action<? super Throwable> onError, FileWatcherListener listener) {
+        try {
+            WatchService watchService = FileSystems.getDefault().newWatchService();
+            WatchServiceFileWatcherBacking backing = new WatchServiceFileWatcherBacking(fileSystemSubset, onError, listener, watchService);
+            return backing.start(executor);
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBacking.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBacking.java
new file mode 100644
index 0000000..4517e7f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBacking.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch.jdk7;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.internal.filewatch.FileWatcher;
+import org.gradle.internal.filewatch.FileWatcherEvent;
+import org.gradle.internal.filewatch.FileWatcherListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.ClosedWatchServiceException;
+import java.nio.file.WatchService;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class WatchServiceFileWatcherBacking {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(WatchServiceFileWatcherBacking.class);
+
+    private final AtomicBoolean started = new AtomicBoolean();
+    private final AtomicBoolean running = new AtomicBoolean();
+    private final AtomicBoolean stopped = new AtomicBoolean();
+
+    private final Action<? super Throwable> onError;
+    private final FileWatcherListener listener;
+    private final WatchService watchService;
+    private final WatchServicePoller poller;
+
+    private final FileWatcher fileWatcher = new FileWatcher() {
+        @Override
+        public boolean isRunning() {
+            return running.get();
+        }
+
+        @Override
+        public void stop() {
+            WatchServiceFileWatcherBacking.this.stop();
+        }
+    };
+
+    WatchServiceFileWatcherBacking(FileSystemSubset fileSystemSubset, Action<? super Throwable> onError, FileWatcherListener listener, WatchService watchService) throws IOException {
+        this.onError = onError;
+        this.listener = new WatchServiceRegistrar(watchService, fileSystemSubset, listener);
+        this.watchService = watchService;
+        this.poller = new WatchServicePoller(watchService);
+    }
+
+    public FileWatcher start(ListeningExecutorService executorService) {
+        if (started.compareAndSet(false, true)) {
+            final ListenableFuture<?> runLoopFuture = executorService.submit(new Runnable() {
+                @Override
+                public void run() {
+                    if (!stopped.get()) {
+                        running.set(true);
+                        try {
+                            try {
+                                pumpEvents();
+                            } catch (InterruptedException e) {
+                                // just stop
+                            } catch (Throwable t) {
+                                stop();
+                                onError.execute(t);
+                            }
+                        } finally {
+                            stop();
+                        }
+                    }
+                }
+            });
+
+            // This is necessary so that the watcher indicates its not running if the runnable gets cancelled
+            Futures.addCallback(runLoopFuture, new FutureCallback<Object>() {
+                @Override
+                public void onSuccess(Object result) {
+                    running.set(false);
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    running.set(false);
+                }
+            });
+            return fileWatcher;
+        } else {
+            throw new IllegalStateException("file watcher is started");
+        }
+    }
+
+    private void pumpEvents() throws InterruptedException {
+        while (isRunning()) {
+            try {
+                List<FileWatcherEvent> events = poller.takeEvents();
+                if (events != null) {
+                    deliverEvents(events);
+                }
+            } catch (ClosedWatchServiceException e) {
+                stop();
+            }
+        }
+    }
+
+    private void deliverEvents(List<FileWatcherEvent> events) {
+        for (FileWatcherEvent event : events) {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Received file system event: {}", event);
+            }
+            if (!isRunning()) {
+                break;
+            }
+            listener.onChange(fileWatcher, event);
+        }
+    }
+
+    private boolean isRunning() {
+        return running.get() && !Thread.currentThread().isInterrupted();
+    }
+
+    private void stop() {
+        if (stopped.compareAndSet(false, true)) {
+            if (running.compareAndSet(true, false)) {
+                try {
+                    watchService.close();
+                } catch (IOException e) {
+                    // ignore exception in shutdown
+                } catch (ClosedWatchServiceException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServicePoller.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServicePoller.java
new file mode 100644
index 0000000..6f3ef3b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServicePoller.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch.jdk7;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.Transformer;
+import org.gradle.internal.Cast;
+import org.gradle.internal.filewatch.FileWatcherEvent;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.*;
+import java.util.Collections;
+import java.util.List;
+
+class WatchServicePoller {
+    private final WatchService watchService;
+
+    WatchServicePoller(WatchService watchService) throws IOException {
+        this.watchService = watchService;
+    }
+
+    @Nullable
+    public List<FileWatcherEvent> takeEvents() throws InterruptedException {
+        WatchKey watchKey = watchService.take();
+        if (watchKey != null) {
+            return handleWatchKey(watchKey);
+        }
+        return null;
+    }
+
+    private List<FileWatcherEvent> handleWatchKey(WatchKey watchKey) {
+        final Path watchedPath = (Path) watchKey.watchable();
+        Transformer<FileWatcherEvent, WatchEvent<?>> watchEventTransformer = new Transformer<FileWatcherEvent, WatchEvent<?>>() {
+            @Override
+            public FileWatcherEvent transform(WatchEvent<?> event) {
+                WatchEvent.Kind kind = event.kind();
+                File file = null;
+                if (kind.type() == Path.class) {
+                    WatchEvent<Path> ev = Cast.uncheckedCast(event);
+                    file = watchedPath.resolve(ev.context()).toFile();
+                }
+                return toEvent(kind, file);
+            }
+        };
+
+        List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
+        watchKey.reset();
+        if (watchEvents.isEmpty()) {
+            return Collections.singletonList(FileWatcherEvent.delete(watchedPath.toFile()));
+        } else {
+            return CollectionUtils.collect(watchEvents, watchEventTransformer);
+        }
+    }
+
+    private FileWatcherEvent toEvent(WatchEvent.Kind kind, File file) {
+        if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
+            return FileWatcherEvent.create(file);
+        } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
+            return FileWatcherEvent.delete(file);
+        } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
+            return FileWatcherEvent.modify(file);
+        } else if (kind == StandardWatchEventKinds.OVERFLOW) {
+            return FileWatcherEvent.undefined();
+        } else {
+            throw new IllegalStateException("Unknown watch kind " + kind);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java
new file mode 100644
index 0000000..da54a9e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch.jdk7;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.sun.nio.file.SensitivityWatchEventModifier;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.internal.FileUtils;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.filewatch.FileWatcher;
+import org.gradle.internal.filewatch.FileWatcherEvent;
+import org.gradle.internal.filewatch.FileWatcherListener;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+
+class WatchServiceRegistrar implements FileWatcherListener {
+    // http://stackoverflow.com/a/18362404
+    // make watch sensitivity as 2 seconds on MacOSX, polls every 2 seconds for changes. Default is 10 seconds.
+    private static final WatchEvent.Modifier[] WATCH_MODIFIERS = new WatchEvent.Modifier[]{SensitivityWatchEventModifier.HIGH};
+    private static final WatchEvent.Kind[] WATCH_KINDS = new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY};
+
+    private final WatchService watchService;
+    private final FileSystemSubset fileSystemSubset;
+    private final FileSystemSubset unfilteredFileSystemSubset;
+    private final FileWatcherListener delegate;
+    private final Iterable<? extends File> roots;
+
+    WatchServiceRegistrar(WatchService watchService, FileSystemSubset fileSystemSubset, FileWatcherListener delegate) throws IOException {
+        this.watchService = watchService;
+        this.fileSystemSubset = fileSystemSubset;
+        this.unfilteredFileSystemSubset = fileSystemSubset.unfiltered();
+        this.roots = fileSystemSubset.getRoots();
+        this.delegate = delegate;
+
+        // Turn the requested watch points into actual enclosing directories that exist
+        Iterable<File> enclosingDirsThatExist = Iterables.transform(roots, new Function<File, File>() {
+            @Override
+            public File apply(File input) {
+                File target = input;
+                while (!target.isDirectory()) {
+                    target = target.getParentFile();
+                }
+                return target;
+            }
+        });
+
+        // Collapse the set
+        Iterable<? extends File> startingWatchPoints = FileUtils.calculateRoots(enclosingDirsThatExist);
+
+        for (File dir : startingWatchPoints) {
+            Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
+                    if (inUnfilteredSubsetOrAncestorOfAnyRoot(path.toFile())) {
+                        watchDir(path);
+                        return FileVisitResult.CONTINUE;
+                    } else {
+                        return FileVisitResult.SKIP_SUBTREE;
+                    }
+                }
+            });
+        }
+    }
+
+    private void watchDir(Path dir) throws IOException {
+        try {
+            dir.register(watchService, WATCH_KINDS, WATCH_MODIFIERS);
+        } catch (IOException e) {
+            // Windows at least will sometimes throw odd exceptions like java.nio.file.AccessDeniedException
+            // if the file gets deleted while the watch is being set up.
+            // So, we just ignore the exception if the dir doesn't exist anymore
+            if (Files.exists(dir)) {
+                throw e;
+            }
+        }
+    }
+
+    private boolean inUnfilteredSubsetOrAncestorOfAnyRoot(File file) {
+        if (unfilteredFileSystemSubset.contains(file)) {
+            return true;
+        } else {
+            String absolutePathWithSeparator = file.getAbsolutePath() + File.separator;
+            for (File root : roots) {
+                if (root.equals(file) || root.getPath().startsWith(absolutePathWithSeparator)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onChange(FileWatcher watcher, FileWatcherEvent event) {
+        if (event.getType().equals(FileWatcherEvent.Type.UNDEFINED) || event.getFile() == null) {
+            delegate.onChange(watcher, event);
+            return;
+        }
+
+        File file = event.getFile();
+        maybeFire(watcher, event);
+
+        if (watcher.isRunning() && file.isDirectory() && event.getType().equals(FileWatcherEvent.Type.CREATE)) {
+            try {
+                newDirectory(watcher, file);
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+    }
+
+    private void maybeFire(FileWatcher watcher, FileWatcherEvent event) {
+        if (fileSystemSubset.contains(event.getFile())) {
+            delegate.onChange(watcher, event);
+        }
+    }
+
+    private void newDirectory(FileWatcher watcher, File dir) throws IOException {
+        if (!watcher.isRunning()) {
+            return;
+        }
+        if (dir.exists()) {
+            watchDir(dir.toPath());
+            File[] contents = dir.listFiles();
+            if (contents != null) {
+                for (File file : contents) {
+                    maybeFire(watcher, FileWatcherEvent.create(file));
+                    if (!watcher.isRunning()) {
+                        return;
+                    }
+
+                    if (file.isDirectory()) {
+                        newDirectory(watcher, file);
+                    }
+                }
+            }
+        }
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationExecutor.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationExecutor.java
new file mode 100644
index 0000000..5e85101
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationExecutor.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.internal.progress;
+
+import org.gradle.internal.Factory;
+
+/**
+ * Runs build operations. These are the pieces of work that make up a build.
+ *
+ * <p>Note that the current implementation is not thread safe. This will happen later.</p>
+ *
+ * <p>This is to be synchronized with {@link org.gradle.internal.operations.BuildOperationProcessor}.
+ */
+public interface BuildOperationExecutor {
+    /**
+     * Runs the given build operation synchronously. Invokes the given factory from the current thread and returns the result.
+     *
+     * <p>Rethrows any exception thrown by the factory.</p>
+     */
+    <T> T run(Object id, BuildOperationType operationType, Factory<T> factory);
+
+    /**
+     * Runs the given build operation synchronously. Invokes the given action from the current thread.
+     *
+     * <p>Rethrows any exception thrown by the action.</p>
+     */
+    void run(Object id, BuildOperationType operationType, Runnable action);
+
+    /**
+     * Returns the id of the current operation.
+     */
+    Object getCurrentOperationId();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationInternal.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationInternal.java
new file mode 100644
index 0000000..e5ee549
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationInternal.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.Nullable;
+
+public final class BuildOperationInternal {
+    private final Object id;
+    private final Object parentId;
+    private final BuildOperationType operationType;
+
+    public BuildOperationInternal(Object id, Object parentId, BuildOperationType operationType) {
+        this.id = id;
+        this.parentId = parentId;
+        this.operationType = operationType;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    @Nullable
+    public Object getParentId() {
+        return parentId;
+    }
+
+    public BuildOperationType getOperationType() {
+        return operationType;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationType.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationType.java
new file mode 100644
index 0000000..b4ac6d5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/BuildOperationType.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Enumerates the build operations tracked by the progress event infrastructure.
+ */
+public enum BuildOperationType {
+
+    RUNNING_BUILD("Run build"),
+    EVALUATING_INIT_SCRIPTS("Run init scripts"),
+    EVALUATING_SETTINGS("Load projects"),
+    CONFIGURING_BUILD("Configure build"),
+    POPULATING_TASK_GRAPH("Calculate task graph"),
+    EXECUTING_TASKS("Run tasks");
+
+    private String name;
+
+    BuildOperationType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDisplayName() {
+        return getName();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutor.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutor.java
new file mode 100644
index 0000000..bf50117
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutor.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.internal.progress;
+
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.UncheckedException;
+
+import java.util.LinkedList;
+
+// TODO - thread safety
+public class DefaultBuildOperationExecutor implements BuildOperationExecutor {
+    private final InternalBuildListener listener;
+    private final TimeProvider timeProvider;
+    private LinkedList<Object> operationStack = new LinkedList<Object>();
+
+    public DefaultBuildOperationExecutor(InternalBuildListener listener, TimeProvider timeProvider) {
+        this.listener = listener;
+        this.timeProvider = timeProvider;
+    }
+
+    @Override
+    public Object getCurrentOperationId() {
+        if (operationStack.isEmpty()) {
+            throw new IllegalStateException("No operation is currently running.");
+        }
+        return operationStack.getFirst();
+    }
+
+    @Override
+    public void run(Object id, BuildOperationType operationType, Runnable action) {
+        run(id, operationType, Factories.toFactory(action));
+    }
+
+    @Override
+    public <T> T run(Object id, BuildOperationType operationType, Factory<T> factory) {
+        Object parentId = operationStack.isEmpty() ? null : operationStack.getFirst();
+        operationStack.addFirst(id);
+        try {
+            long startTime = timeProvider.getCurrentTime();
+            BuildOperationInternal operation = new BuildOperationInternal(id, parentId, operationType);
+            listener.started(operation, new OperationStartEvent(startTime));
+
+            T result = null;
+            Throwable failure = null;
+            try {
+                result = factory.create();
+            } catch (Throwable t) {
+                failure = t;
+            }
+
+            long endTime = timeProvider.getCurrentTime();
+            listener.finished(operation, new OperationResult(startTime, endTime, failure));
+
+            if (failure != null) {
+                throw UncheckedException.throwAsUncheckedException(failure);
+            }
+
+            return result;
+        } finally {
+            operationStack.removeFirst();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/InternalBuildListener.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/InternalBuildListener.java
new file mode 100644
index 0000000..f9984d8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/InternalBuildListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 interface InternalBuildListener {
+
+    void started(BuildOperationInternal buildOperation, OperationStartEvent startEvent);
+
+    void finished(BuildOperationInternal buildOperation, OperationResult finishEvent);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdGenerator.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdGenerator.java
new file mode 100644
index 0000000..85d7341
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdGenerator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.Task;
+import org.gradle.api.invocation.Gradle;
+
+/**
+ * Generates operation ids used by the progress event infrastructure.
+ */
+public final class OperationIdGenerator {
+
+    /**
+     * Generates an operation id for the given task.
+     *
+     * @param task the task operation
+     * @return the operation id
+     */
+    public static Object generateId(Task task) {
+        return generateId(task.getProject().getGradle()) + "-" + task.getPath();
+    }
+
+    /**
+     * Generates an operation id for the given build operation type.
+     *
+     * @param operationType the build operation type
+     * @param gradle te Gradle instance to which the build operation belongs
+     * @return the operation id
+     */
+    public static Object generateId(BuildOperationType operationType, Gradle gradle) {
+        return generateId(gradle) + "-" + operationType.getName();
+    }
+
+    /**
+     * Generates an operation id for the given Gradle instance.
+     *
+     * @param gradle the Gradle instance
+     * @return the operation id
+     */
+    public static Object generateId(Gradle gradle) {
+        return gradle == null ? null : String.valueOf(System.identityHashCode(gradle));
+    }
+
+}
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
index 0d796a2..2f16283 100644
--- a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdentifier.java
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationIdentifier.java
@@ -16,7 +16,9 @@
 
 package org.gradle.internal.progress;
 
-public class OperationIdentifier {
+import java.io.Serializable;
+
+public class OperationIdentifier implements Serializable {
     private final long id;
     private final Long parentId;
 
@@ -37,4 +39,29 @@ public class OperationIdentifier {
     public String toString() {
         return id + ":" + parentId;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        OperationIdentifier that = (OperationIdentifier) o;
+
+        if (getId() != that.getId()) {
+            return false;
+        }
+        return !(getParentId() != null ? !getParentId().equals(that.getParentId()) : that.getParentId() != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (getId() ^ (getId() >>> 32));
+        result = 31 * result + (getParentId() != null ? getParentId().hashCode() : 0);
+        return result;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationResult.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationResult.java
new file mode 100644
index 0000000..3ae7538
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationResult.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.internal.progress;
+
+import org.gradle.api.Nullable;
+
+public class OperationResult {
+    private final long startTime;
+    private final long endTime;
+    private final Throwable failure;
+
+    public OperationResult(long startTime, long endTime, Throwable failure) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.failure = failure;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    @Nullable
+    public Throwable getFailure() {
+        return failure;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationStartEvent.java b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationStartEvent.java
new file mode 100644
index 0000000..bffdb82
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationStartEvent.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.internal.progress;
+
+public class OperationStartEvent {
+    private final long startTime;
+
+    public OperationStartEvent(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+}
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
index c18d2d9..c3d5308 100644
--- a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchy.java
@@ -43,16 +43,16 @@ public class OperationsHierarchy {
         return id;
     }
 
-    public long currentOperationId() {
+    public OperationIdentifier currentOperationId() {
         assertOperationStarted();
-        return id.getId();
+        return id;
     }
 
-    public long completeCurrentOperation() {
-        assertOperationStarted();
+    public OperationIdentifier completeCurrentOperation() {
+        OperationIdentifier currentOp = currentOperationId();
         assertHierarchyNotEmpty();
         Long last = hierarchy.getLast();
-        if (id.getId() == last) {
+        if (currentOp.getId() == last) {
             //typical scenario
             hierarchy.removeLast();
         } else {
@@ -60,11 +60,10 @@ public class OperationsHierarchy {
             //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());
+            hierarchy.remove(currentOp.getId());
         }
-        long out = id.getId();
         id = null;
-        return out;
+        return currentOp;
     }
 
     private void assertOperationStarted() {
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
index f9b6c69..836052c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchyKeeper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/progress/OperationsHierarchyKeeper.java
@@ -31,7 +31,7 @@ public class OperationsHierarchyKeeper {
         if (h == null) {
             h = new LinkedList<Long>();
             if (parentHint != null) {
-                h.add(parentHint.currentOperationId());
+                h.add(parentHint.currentOperationId().getId());
             }
             hierarchy.set(h);
         }
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
index 31a95b9..b89b9a7 100644
--- 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
@@ -64,6 +64,9 @@ import org.gradle.internal.event.ListenerManager;
 import org.gradle.internal.id.LongIdGenerator;
 import org.gradle.internal.operations.logging.BuildOperationLoggerFactory;
 import org.gradle.internal.operations.logging.DefaultBuildOperationLoggerFactory;
+import org.gradle.internal.progress.BuildOperationExecutor;
+import org.gradle.internal.progress.DefaultBuildOperationExecutor;
+import org.gradle.internal.progress.InternalBuildListener;
 import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -118,6 +121,10 @@ public class BuildScopeServices extends DefaultServiceRegistry {
         return listenerManager.createChild();
     }
 
+    protected BuildOperationExecutor createBuildOperationExecutor(ListenerManager listenerManager, TimeProvider timeProvider) {
+        return new DefaultBuildOperationExecutor(listenerManager.getBroadcaster(InternalBuildListener.class), timeProvider);
+    }
+
     protected ClassPathRegistry createClassPathRegistry() {
         return new DefaultClassPathRegistry(
                 new DefaultClassPathProvider(get(ModuleRegistry.class)),
@@ -330,4 +337,5 @@ public class BuildScopeServices extends DefaultServiceRegistry {
     protected BuildOperationLoggerFactory createBuildOperationLoggerFactory() {
         return new DefaultBuildOperationLoggerFactory();
     }
+
 }
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
index c11844f..a242354 100755
--- 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
@@ -36,10 +36,7 @@ import org.gradle.cache.internal.locklistener.FileLockContentionHandler;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.configuration.DefaultImportsReader;
 import org.gradle.configuration.ImportsReader;
-import org.gradle.initialization.ClassLoaderRegistry;
-import org.gradle.initialization.DefaultClassLoaderRegistry;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.initialization.*;
 import org.gradle.internal.classloader.ClassLoaderFactory;
 import org.gradle.internal.classloader.DefaultClassLoaderFactory;
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
@@ -47,6 +44,8 @@ import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.environment.GradleBuildEnvironment;
 import org.gradle.internal.event.DefaultListenerManager;
 import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.filewatch.DefaultFileWatcherFactory;
+import org.gradle.internal.filewatch.FileWatcherFactory;
 import org.gradle.internal.nativeintegration.ProcessEnvironment;
 import org.gradle.internal.nativeintegration.filesystem.FileSystem;
 import org.gradle.internal.reflect.DirectInstantiator;
@@ -92,7 +91,7 @@ public class GlobalScopeServices {
         }
     }
 
-    DefaultGradleLauncherFactory createGradleLauncherFactory(ServiceRegistry services) {
+    GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry services) {
         return new DefaultGradleLauncherFactory(services);
     }
 
@@ -110,9 +109,9 @@ public class GlobalScopeServices {
 
     ClassPathRegistry createClassPathRegistry(ModuleRegistry moduleRegistry, PluginModuleRegistry pluginModuleRegistry) {
         return new DefaultClassPathRegistry(
-                new DefaultClassPathProvider(moduleRegistry),
-                new DynamicModulesClassPathProvider(moduleRegistry,
-                        pluginModuleRegistry));
+            new DefaultClassPathProvider(moduleRegistry),
+            new DynamicModulesClassPathProvider(moduleRegistry,
+                pluginModuleRegistry));
     }
 
     DefaultModuleRegistry createModuleRegistry() {
@@ -165,9 +164,9 @@ public class GlobalScopeServices {
 
     FileLockManager createFileLockManager(ProcessEnvironment processEnvironment, FileLockContentionHandler fileLockContentionHandler) {
         return new DefaultFileLockManager(
-                new DefaultProcessMetaDataProvider(
-                        processEnvironment),
-                fileLockContentionHandler);
+            new DefaultProcessMetaDataProvider(
+                processEnvironment),
+            fileLockContentionHandler);
     }
 
     InMemoryTaskArtifactCache createInMemoryTaskArtifactCache() {
@@ -176,8 +175,8 @@ public class GlobalScopeServices {
 
     DefaultFileLockContentionHandler createFileLockContentionHandler(ExecutorFactory executorFactory, MessagingServices messagingServices) {
         return new DefaultFileLockContentionHandler(
-                executorFactory,
-                messagingServices.get(InetAddressFactory.class)
+            executorFactory,
+            messagingServices.get(InetAddressFactory.class)
         );
     }
 
@@ -233,5 +232,7 @@ public class GlobalScopeServices {
         return new DefaultImportsReader();
     }
 
-
+    FileWatcherFactory createFileWatcherFactory(ExecutorFactory executorFactory) {
+        return new DefaultFileWatcherFactory(executorFactory);
+    }
 }
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
index 6b44446..51f1315 100644
--- 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
@@ -21,6 +21,7 @@ import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.plugins.*;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.options.OptionReader;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.*;
@@ -29,8 +30,11 @@ import org.gradle.execution.commandline.CommandLineTaskParser;
 import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter;
 import org.gradle.execution.taskgraph.TaskPlanExecutor;
 import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
 import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.progress.BuildOperationExecutor;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistration;
@@ -93,8 +97,14 @@ public class GradleScopeServices extends DefaultServiceRegistry {
         };
     }
 
-    TaskGraphExecuter createTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor, BuildCancellationToken cancellationToken) {
-        return new DefaultTaskGraphExecuter(listenerManager, taskPlanExecutor, cancellationToken);
+    TaskGraphExecuter createTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor, BuildCancellationToken cancellationToken, TimeProvider timeProvider, BuildOperationExecutor buildOperationExecutor) {
+        Factory<TaskExecuter> taskExecuterFactory = new Factory<TaskExecuter>() {
+            @Override
+            public TaskExecuter create() {
+                return get(TaskExecuter.class);
+            }
+        };
+        return new DefaultTaskGraphExecuter(listenerManager, taskPlanExecutor, taskExecuterFactory, cancellationToken, timeProvider, buildOperationExecutor);
     }
 
     ServiceRegistryFactory createServiceRegistryFactory(final ServiceRegistry services) {
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
index d6911a6..9c08369 100644
--- 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
@@ -17,6 +17,7 @@ package org.gradle.internal.service.scopes;
 
 import org.gradle.StartParameter;
 import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.execution.internal.TaskInputsListener;
 import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
 import org.gradle.api.internal.changedetection.changes.DefaultTaskArtifactStateRepository;
 import org.gradle.api.internal.changedetection.changes.ShortCircuitTaskArtifactStateRepository;
@@ -31,26 +32,42 @@ 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.event.ListenerManager;
 import org.gradle.internal.id.RandomLongIdGenerator;
 import org.gradle.internal.operations.BuildOperationProcessor;
 import org.gradle.internal.operations.DefaultBuildOperationProcessor;
 import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.event.ListenerManager;
 import org.gradle.internal.serialize.DefaultSerializerRegistry;
 import org.gradle.internal.serialize.SerializerRegistry;
 
 public class TaskExecutionServices {
-    TaskExecuter createTaskExecuter(TaskArtifactStateRepository repository, ListenerManager listenerManager) {
+
+    TaskExecuter createTaskExecuter(TaskArtifactStateRepository repository, ListenerManager listenerManager, Gradle gradle) {
+        // TODO - need a more comprehensible way to only collect inputs for the outer build
+        //      - we are trying to ignore buildSrc here, but also avoid weirdness with use of GradleBuild tasks
+        boolean isOuterBuild = gradle.getParent() == null;
+        TaskInputsListener taskInputsListener = isOuterBuild
+            ? listenerManager.getBroadcaster(TaskInputsListener.class)
+            : TaskInputsListener.NOOP;
+
         return new ExecuteAtMostOnceTaskExecuter(
-                new SkipOnlyIfTaskExecuter(
-                        new SkipTaskWithNoActionsExecuter(
-                                new SkipEmptySourceFilesTaskExecuter(
-                                        new ValidatingTaskExecuter(
-                                                new SkipUpToDateTaskExecuter(repository,
-                                                        new PostExecutionAnalysisTaskExecuter(
-                                                                new ExecuteActionsTaskExecuter(
-                                                                        listenerManager.getBroadcaster(TaskActionListener.class)
-                                                                ))))))));
+            new SkipOnlyIfTaskExecuter(
+                new SkipTaskWithNoActionsExecuter(
+                    new SkipEmptySourceFilesTaskExecuter(
+                        taskInputsListener,
+                        new ValidatingTaskExecuter(
+                            new SkipUpToDateTaskExecuter(repository,
+                                new PostExecutionAnalysisTaskExecuter(
+                                    new ExecuteActionsTaskExecuter(
+                                        listenerManager.getBroadcaster(TaskActionListener.class)
+                                    )
+                                )
+                            )
+                        )
+                    )
+                )
+            )
+        );
     }
 
     TaskArtifactStateCacheAccess createCacheAccess(Gradle gradle, CacheRepository cacheRepository, InMemoryTaskArtifactCache inMemoryTaskArtifactCache, GradleBuildEnvironment environment) {
@@ -77,19 +94,19 @@ public class TaskExecutionServices {
         outputFilesSnapshotter.registerSerializers(serializerRegistry);
 
         TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess,
-                new CacheBackedFileSnapshotRepository(cacheAccess,
-                        serializerRegistry.build(),
-                        new RandomLongIdGenerator()));
+            new CacheBackedFileSnapshotRepository(cacheAccess,
+                serializerRegistry.build(),
+                new RandomLongIdGenerator()));
 
         return new ShortCircuitTaskArtifactStateRepository(
-                        startParameter,
-                        instantiator,
-                        new DefaultTaskArtifactStateRepository(
-                                taskHistoryRepository,
-                                instantiator,
-                                outputFilesSnapshotter,
-                                fileCollectionSnapshotter
-                        )
+            startParameter,
+            instantiator,
+            new DefaultTaskArtifactStateRepository(
+                taskHistoryRepository,
+                instantiator,
+                outputFilesSnapshotter,
+                fileCollectionSnapshotter
+            )
         );
     }
 
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
index a79b703..c387051 100644
--- a/subprojects/core/src/main/groovy/org/gradle/internal/text/TreeFormatter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/internal/text/TreeFormatter.java
@@ -80,7 +80,7 @@ public class TreeFormatter extends TreeVisitor<String> {
             node.prefix = node.isRoot() ? "" : node.parent.prefix + "    ";
         }
 
-        StyledTextOutput output = new LinePrefixingStyledTextOutput(original, node.prefix);
+        StyledTextOutput output = new LinePrefixingStyledTextOutput(original, node.prefix, false);
         if (!node.valueWritten) {
             output.append(node.parent.prefix);
             output.append("  - ");
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 bfe6679..ca16f67 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/ProgressLogger.java
@@ -16,6 +16,8 @@
 
 package org.gradle.logging;
 
+import org.gradle.internal.progress.OperationIdentifier;
+
 /**
  * Used to log the progress of a potentially long running operation.
  *
@@ -124,5 +126,5 @@ public interface ProgressLogger {
      */
     void completed(String status);
 
-    long currentOperationId();
+    OperationIdentifier currentOperationId();
 }
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 b8d0849..a5eab98 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
@@ -35,14 +35,14 @@ public class ConsoleBackedProgressRenderer implements OutputEventListener {
         try {
             if (event instanceof ProgressStartEvent) {
                 ProgressStartEvent startEvent = (ProgressStartEvent) event;
-                ProgressOperation op = operations.start(startEvent.getShortDescription(), startEvent.getStatus(), startEvent.getOperationId(), startEvent.getParentOperationId());
+                ProgressOperation op = operations.start(startEvent.getShortDescription(), startEvent.getStatus(), startEvent.getOperationId().getId(), startEvent.getOperationId().getParentId());
                 updateText(op);
             } else if (event instanceof ProgressCompleteEvent) {
-                ProgressOperation op = operations.complete(((ProgressCompleteEvent) event).getOperationId());
+                ProgressOperation op = operations.complete(((ProgressCompleteEvent) event).getOperationId().getId());
                 updateText(op.getParent());
             } else if (event instanceof ProgressEvent) {
                 ProgressEvent progressEvent = (ProgressEvent) event;
-                ProgressOperation op = operations.progress(progressEvent.getStatus(), progressEvent.getOperationId());
+                ProgressOperation op = operations.progress(progressEvent.getStatus(), progressEvent.getOperationId().getId());
                 updateText(op);
             }
             listener.onOutput(event);
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 b11f638..a868482 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
@@ -117,7 +117,7 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
             assertNotCompleted();
             state = State.started;
             OperationIdentifier id = hierarchy.start();
-            listener.started(new ProgressStartEvent(id.getId(), id.getParentId(), timeProvider.getCurrentTime(), category, description, shortDescription, loggingHeader, toStatus(status)));
+            listener.started(new ProgressStartEvent(id, timeProvider.getCurrentTime(), category, description, shortDescription, loggingHeader, toStatus(status)));
         }
 
         public void progress(String status) {
@@ -138,7 +138,7 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
                     timeProvider.getCurrentTime(), category, description, toStatus(status)));
         }
 
-        public long currentOperationId() {
+        public OperationIdentifier currentOperationId() {
             return hierarchy.currentOperationId();
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java
index 3a7d17a..cb2616a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java
@@ -23,14 +23,25 @@ import org.gradle.logging.StyledTextOutput;
 public class LinePrefixingStyledTextOutput extends AbstractLineChoppingStyledTextOutput {
     private final StyledTextOutput output;
     private final CharSequence prefix;
+    private boolean prefixFirstLine;
+    private boolean prefixed;
 
     public LinePrefixingStyledTextOutput(StyledTextOutput output, CharSequence prefix) {
+       this(output, prefix, true);
+    }
+
+    public LinePrefixingStyledTextOutput(StyledTextOutput output, CharSequence prefix, boolean prefixFirstLine) {
         this.output = output;
         this.prefix = prefix;
+        this.prefixFirstLine = prefixFirstLine;
     }
 
     @Override
     protected void doLineText(CharSequence text, boolean terminatesLine) {
+        if (!prefixed && prefixFirstLine) {
+            output.text(prefix);
+            prefixed = true;
+        }
         output.text(text);
     }
 
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 6830ea9..bb65692 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
@@ -16,13 +16,14 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.progress.OperationIdentifier;
 
 public class ProgressCompleteEvent extends CategorisedOutputEvent {
     private final String status;
     private final String description;
-    private long operationId;
+    private OperationIdentifier operationId;
 
-    public ProgressCompleteEvent(long operationId, long timestamp, String category, String description, String status) {
+    public ProgressCompleteEvent(OperationIdentifier operationId, long timestamp, String category, String description, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
         this.operationId = operationId;
         this.status = status;
@@ -42,7 +43,7 @@ public class ProgressCompleteEvent extends CategorisedOutputEvent {
         return String.format("ProgressComplete %s", status);
     }
 
-    public long getOperationId() {
+    public OperationIdentifier 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 62f5795..e7f2f95 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
@@ -16,12 +16,13 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.progress.OperationIdentifier;
 
 public class ProgressEvent extends CategorisedOutputEvent {
     private final String status;
-    private long operationId;
+    private OperationIdentifier operationId;
 
-    public ProgressEvent(long operationId, long timestamp, String category, String status) {
+    public ProgressEvent(OperationIdentifier operationId, long timestamp, String category, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
         this.operationId = operationId;
         this.status = status;
@@ -36,7 +37,7 @@ public class ProgressEvent extends CategorisedOutputEvent {
         return String.format("Progress %s", status);
     }
 
-    public long getOperationId() {
+    public OperationIdentifier getOperationId() {
         return operationId;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
index 68230aa..9d38d75 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
@@ -17,9 +17,11 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.progress.OperationIdentifier;
 import org.gradle.util.GUtil;
 
-import java.util.LinkedList;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import static org.gradle.logging.StyledTextOutput.Style;
 
@@ -32,7 +34,7 @@ public class ProgressLogEventGenerator implements OutputEventListener {
 
     private final OutputEventListener listener;
     private final boolean deferHeader;
-    private final LinkedList<Operation> operations = new LinkedList<Operation>();
+    private final Map<OperationIdentifier, Operation> operations = new LinkedHashMap<OperationIdentifier, Operation>();
 
     public ProgressLogEventGenerator(OutputEventListener listener, boolean deferHeader) {
         this.listener = listener;
@@ -52,7 +54,7 @@ public class ProgressLogEventGenerator implements OutputEventListener {
     }
 
     private void doOutput(RenderableOutputEvent event) {
-        for (Operation operation : operations) {
+        for (Operation operation : operations.values()) {
             operation.completeHeader();
         }
         listener.onOutput(event);
@@ -60,7 +62,12 @@ public class ProgressLogEventGenerator implements OutputEventListener {
 
     private void onComplete(ProgressCompleteEvent progressCompleteEvent) {
         assert !operations.isEmpty();
-        Operation operation = operations.removeLast();
+        Operation operation = operations.remove(progressCompleteEvent.getOperationId());
+        assert operation!=null;
+        completeOperation(progressCompleteEvent, operation);
+    }
+
+    private void completeOperation(ProgressCompleteEvent progressCompleteEvent, Operation operation) {
         operation.status = progressCompleteEvent.getStatus();
         operation.completeTime = progressCompleteEvent.getTimestamp();
         operation.complete();
@@ -68,7 +75,7 @@ public class ProgressLogEventGenerator implements OutputEventListener {
 
     private void onStart(ProgressStartEvent progressStartEvent) {
         Operation operation = new Operation(progressStartEvent.getCategory(), progressStartEvent.getLoggingHeader(), progressStartEvent.getTimestamp());
-        operations.add(operation);
+        operations.put(progressStartEvent.getOperationId(), operation);
 
         if (!deferHeader || !(progressStartEvent.getLoggingHeader() != null && progressStartEvent.getLoggingHeader().equals(progressStartEvent.getShortDescription()))) {
             operation.startHeader();
@@ -90,11 +97,11 @@ public class ProgressLogEventGenerator implements OutputEventListener {
             this.category = category;
             this.loggingHeader = loggingHeader;
             this.startTime = startTime;
-            hasLoggingHeader = GUtil.isTrue(loggingHeader);
+            this.hasLoggingHeader = GUtil.isTrue(loggingHeader);
         }
 
         private void doOutput(RenderableOutputEvent event) {
-            for (Operation pending : operations) {
+            for (Operation pending : operations.values()) {
                 if (pending == this) {
                     break;
                 }
@@ -114,10 +121,9 @@ public class ProgressLogEventGenerator implements OutputEventListener {
         }
 
         public void completeHeader() {
-            boolean hasDescription = GUtil.isTrue(loggingHeader);
             switch (state) {
                 case None:
-                    if (hasDescription) {
+                    if (hasLoggingHeader) {
                         listener.onOutput(new StyledTextOutputEvent(startTime, category, LogLevel.LIFECYCLE, loggingHeader + EOL));
                     }
                     break;
@@ -127,7 +133,7 @@ public class ProgressLogEventGenerator implements OutputEventListener {
                 case HeaderCompleted:
                     return;
                 default:
-                    throw new IllegalStateException();
+                    throw new IllegalStateException("state is " + state);
             }
             state = State.HeaderCompleted;
         }
@@ -165,7 +171,7 @@ public class ProgressLogEventGenerator implements OutputEventListener {
                     }
                     break;
                 default:
-                    throw new IllegalStateException();
+                    throw new IllegalStateException("state is " + state);
             }
             state = State.Completed;
         }
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 718eca8..bc4db32 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
@@ -16,19 +16,18 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.progress.OperationIdentifier;
 
 public class ProgressStartEvent extends CategorisedOutputEvent {
-    private long operationId;
-    private Long parentOperationId;
+    private OperationIdentifier operationId;
     private final String description;
     private final String shortDescription;
     private final String loggingHeader;
     private final String status;
 
-    public ProgressStartEvent(long operationId, Long parentOperationId, long timestamp, String category, String description, String shortDescription, String loggingHeader, String status) {
+    public ProgressStartEvent(OperationIdentifier operationId, 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;
@@ -56,11 +55,7 @@ public class ProgressStartEvent extends CategorisedOutputEvent {
         return String.format("ProgressStart %s", description);
     }
 
-    public long getOperationId() {
+    public OperationIdentifier getOperationId() {
         return operationId;
     }
-
-    public Long getParentOperationId() {
-        return parentOperationId;
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/BridgedCollections.java b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/BridgedCollections.java
index 4ca41bd..afdc771 100644
--- a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/BridgedCollections.java
+++ b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/BridgedCollections.java
@@ -17,18 +17,16 @@
 package org.gradle.model.collection.internal;
 
 import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectCollection;
 import org.gradle.api.Namer;
 import org.gradle.api.Transformer;
-import org.gradle.api.internal.PolymorphicDomainObjectContainerInternal;
 import org.gradle.internal.BiAction;
-import org.gradle.internal.Transformers;
 import org.gradle.model.internal.core.*;
-import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.core.rule.describe.StandardDescriptorFactory;
 import org.gradle.model.internal.type.ModelType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collection;
 import java.util.List;
 
 public abstract class BridgedCollections {
@@ -38,176 +36,78 @@ public abstract class BridgedCollections {
     private BridgedCollections() {
     }
 
-    public static <I> ModelReference<NamedEntityInstantiator<I>> instantiatorReference(ModelPath containerPath, ModelType<I> itemType) {
-        final String instantiatorNodeName = "__instantiator";
-        return ModelReference.of(
-                containerPath.child(instantiatorNodeName),
-                new ModelType.Builder<NamedEntityInstantiator<I>>() {
-                }.where(new ModelType.Parameter<I>() {
-                }, itemType).build()
-        );
-    }
-
-    private static class ContainerInfo<I> {
-        final ModelCreators.Builder creatorBuilder;
-        final ModelReference<NamedEntityInstantiator<I>> instantiatorReference;
-        final ModelReference<? extends Collection<I>> storeReference;
-
-        public ContainerInfo(ModelCreators.Builder creatorBuilder, ModelReference<NamedEntityInstantiator<I>> instantiatorReference, ModelReference<? extends Collection<I>> storeReference) {
-            this.creatorBuilder = creatorBuilder;
-            this.instantiatorReference = instantiatorReference;
-            this.storeReference = storeReference;
-        }
-    }
-
-    private static <I, C extends PolymorphicDomainObjectContainerInternal<I>> ContainerInfo<I> creator(
-            final ModelRegistry modelRegistry,
-            final ModelReference<C> containerReference,
-            final ModelType<I> itemType,
-            final Transformer<? extends C, ? super MutableModelNode> containerFactory,
-            final Namer<? super I> namer,
-            String descriptor,
-            final Transformer<String, String> itemDescriptorGenerator
+    public static <I, C extends NamedDomainObjectCollection<I>> ModelCreators.Builder creator(
+        final ModelReference<C> containerReference,
+        final Transformer<? extends C, ? super MutableModelNode> containerFactory,
+        final Namer<? super I> namer,
+        String descriptor,
+        final Transformer<String, String> itemDescriptorGenerator
     ) {
-        assert containerReference.getPath() != null : "container reference path cannot be null";
-
-        final String storeNodeName = "__store";
-
-        final ModelReference<NamedEntityInstantiator<I>> instantiatorReference = instantiatorReference(containerReference.getPath(), itemType);
-
-        final ModelReference<C> storeReference = ModelReference.of(
-                containerReference.getPath().child(storeNodeName),
-                containerReference.getType()
-        );
-
-        ModelCreators.Builder creatorBuilder = ModelCreators.of(
-                containerReference,
-                new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                    public void execute(final MutableModelNode containerNode, List<ModelView<?>> inputs) {
-
-                        C container = containerFactory.transform(containerNode);
-
-                        ModelCreator storeCreator = ModelCreators.bridgedInstance(storeReference, container)
-                                .ephemeral(true)
-                                .hidden(true)
-                                .descriptor(itemDescriptorGenerator.transform(storeNodeName))
-                                .build();
-
-                        modelRegistry.createOrReplace(storeCreator);
-
-                        @SuppressWarnings("ConstantConditions")
-                        String instantiatorNodeName = instantiatorReference.getPath().getName();
-
-                        ModelCreator instantiatorCreator = ModelCreators.bridgedInstance(instantiatorReference, container.getEntityInstantiator())
-                                .ephemeral(true)
-                                .hidden(true)
-                                .descriptor(itemDescriptorGenerator.transform(instantiatorNodeName))
-                                .build();
-
-                        modelRegistry.createOrReplace(instantiatorCreator);
-
-                        containerNode.setPrivateData(containerReference.getType(), container);
-                        container.all(new Action<I>() {
-                            public void execute(final I item) {
-                                final String name = namer.determineName(item);
-
-                                // For now, ignore elements added after the container has been closed
-                                if (!containerNode.isMutable()) {
-                                    LOGGER.debug("Ignoring element '{}' added to '{}' after it is closed.", containerReference.getPath(), name);
-                                    return;
-                                }
-
-                                if (!containerNode.hasLink(name)) {
-                                    ModelType<I> itemType = ModelType.typeOf(item);
-                                    ModelReference<I> itemReference = ModelReference.of(containerReference.getPath().child(name), itemType);
-                                    ModelCreator itemCreator = ModelCreators.of(itemReference, new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                                        @Override
-                                        public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
-                                            C container = ModelViews.assertType(modelViews.get(0), storeReference.getType()).getInstance();
-                                            I item = container.getByName(name);
-                                            modelNode.setPrivateData(ModelType.typeOf(item), item);
-                                        }
-                                    })
-                                            .inputs(storeReference)
-                                            .withProjection(new UnmanagedModelProjection<I>(itemType, true, true))
-                                            .descriptor(itemDescriptorGenerator.transform(name)).build();
-
-                                    containerNode.addLink(itemCreator);
-                                }
+        final ModelPath containerPath = containerReference.getPath();
+        final ModelType<C> containerType = containerReference.getType();
+        assert containerPath != null : "container reference path cannot be null";
+
+        return ModelCreators.of(
+            containerPath,
+            new BiAction<MutableModelNode, List<ModelView<?>>>() {
+                public void execute(final MutableModelNode containerNode, List<ModelView<?>> inputs) {
+                    final C container = containerFactory.transform(containerNode);
+                    containerNode.setPrivateData(containerType, container);
+                    container.all(new Action<I>() {
+                        public void execute(final I item) {
+                            final String name = namer.determineName(item);
+
+                            // For now, ignore elements added after the container has been closed
+                            if (!containerNode.isMutable()) {
+                                LOGGER.debug("Ignoring element '{}' added to '{}' after it is closed.", containerPath, name);
+                                return;
                             }
-                        });
-                        container.whenObjectRemoved(new Action<I>() {
-                            public void execute(I item) {
-                                String name = namer.determineName(item);
-                                containerNode.removeLink(name);
+
+                            if (!containerNode.hasLink(name)) {
+                                ModelType<I> itemType = ModelType.typeOf(item);
+                                ModelCreator itemCreator = ModelCreators
+                                    .unmanagedInstanceOf(
+                                        ModelReference.of(containerPath.child(name), itemType),
+                                        new ExtractFromParentContainer<I, C>(name, containerType)
+                                    )
+                                    .descriptor(itemDescriptorGenerator.transform(name))
+                                    .build();
+                                containerNode.addLink(itemCreator);
                             }
-                        });
-                    }
+                        }
+                    });
+                    container.whenObjectRemoved(new Action<I>() {
+                        public void execute(I item) {
+                            String name = namer.determineName(item);
+                            containerNode.removeLink(name);
+                        }
+                    });
                 }
+            }
         )
-                .ephemeral(true)
-                .descriptor(descriptor);
-
-        return new ContainerInfo<I>(creatorBuilder, instantiatorReference, storeReference);
-    }
-
-    public static <I, C extends PolymorphicDomainObjectContainerInternal<I>, P /* super C */> void dynamicTypes(
-            ModelRegistry modelRegistry,
-            ModelPath modelPath,
-            String descriptor,
-            ModelType<P> publicType,
-            ModelType<C> containerType,
-            ModelType<I> itemType,
-            C container,
-            Namer<? super I> namer,
-            Transformer<String, String> itemDescriptorGenerator
-    ) {
-        ModelReference<C> containerReference = ModelReference.of(modelPath, containerType);
-
-        ContainerInfo<I> containerInfo = creator(modelRegistry, containerReference, itemType, Transformers.constant(container), namer, descriptor, itemDescriptorGenerator);
-
-        modelRegistry.createOrReplace(containerInfo.creatorBuilder
-                .withProjection(new DynamicTypesDomainObjectContainerModelProjection<C, I>(container, itemType, containerInfo.instantiatorReference, containerInfo.storeReference))
-                .withProjection(new UnmanagedModelProjection<P>(publicType, true, true))
-                .build());
-    }
-
-    public static <I, C extends PolymorphicDomainObjectContainerInternal<I>, P /* super C */> void staticTypes(
-            ModelRegistry modelRegistry,
-            ModelPath modelPath,
-            ModelType<C> containerType,
-            ModelType<I> itemType,
-            ModelType<P> publicType,
-            Transformer<? extends C, ? super MutableModelNode> containerFactory,
-            Namer<? super I> namer,
-            String descriptor,
-            Transformer<String, String> itemDescriptorGenerator
-    ) {
-        ModelReference<C> containerReference = ModelReference.of(modelPath, containerType);
-
-        ContainerInfo<I> containerInfo = creator(modelRegistry, containerReference, itemType, containerFactory, namer, descriptor, itemDescriptorGenerator);
-
-        modelRegistry.createOrReplace(containerInfo.creatorBuilder
-                .withProjection(new StaticTypeDomainObjectContainerModelProjection<C, I>(containerType, itemType, containerInfo.instantiatorReference, containerInfo.storeReference))
-                .withProjection(new UnmanagedModelProjection<P>(publicType, true, true))
-                .build());
+            .ephemeral(true)
+            .descriptor(descriptor);
     }
 
     public static Transformer<String, String> itemDescriptor(String parentDescriptor) {
-        return new StandardItemDescriptorFactory(parentDescriptor);
+        return new StandardDescriptorFactory(parentDescriptor);
     }
 
-    private static class StandardItemDescriptorFactory implements Transformer<String, String> {
-        private final String descriptor;
+    private static class ExtractFromParentContainer<I, C extends NamedDomainObjectCollection<I>> implements Transformer<I, MutableModelNode> {
 
-        public StandardItemDescriptorFactory(String descriptor) {
-            this.descriptor = descriptor;
+        private final String name;
+        private final ModelType<C> containerType;
+
+        public ExtractFromParentContainer(String name, ModelType<C> containerType) {
+            this.name = name;
+            this.containerType = containerType;
         }
 
         @Override
-        public String transform(String s) {
-            return descriptor + '.' + s + "()";
+        public I transform(MutableModelNode modelNode) {
+            return modelNode.getParent().getPrivateData(containerType).getByName(name);
         }
     }
+
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DomainObjectContainerModelProjection.java b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DomainObjectContainerModelProjection.java
deleted file mode 100644
index 765079d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DomainObjectContainerModelProjection.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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.model.collection.internal;
-
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Iterables;
-import org.gradle.api.Nullable;
-import org.gradle.api.internal.PolymorphicDomainObjectContainerInternal;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.internal.core.*;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-import org.gradle.model.internal.type.ModelType;
-import org.gradle.util.CollectionUtils;
-
-import java.util.Collection;
-import java.util.List;
-
-public abstract class DomainObjectContainerModelProjection<C extends PolymorphicDomainObjectContainerInternal<M>, M> implements ModelProjection {
-
-    protected final Class<M> baseItemType;
-    private final ModelReference<NamedEntityInstantiator<M>> instantiatorModelReference;
-    private final ModelType<M> baseItemModelType;
-    private final ModelReference<? extends Collection<? super M>> storeReference;
-
-    public DomainObjectContainerModelProjection(ModelType<M> baseItemType, ModelReference<NamedEntityInstantiator<M>> instantiatorModelReference, ModelReference<? extends Collection<? super M>> storeReference) {
-        this.baseItemModelType = baseItemType;
-        this.storeReference = storeReference;
-        this.baseItemType = baseItemType.getConcreteClass();
-        this.instantiatorModelReference = instantiatorModelReference;
-    }
-
-    public <T> ModelView<? extends T> asWritable(ModelType<T> targetType, MutableModelNode node, ModelRuleDescriptor ruleDescriptor, List<ModelView<?>> inputs) {
-        Class<? extends M> itemType = itemType(targetType);
-        if (itemType != null) {
-            return toView(ruleDescriptor, node, itemType);
-        }
-        return null;
-    }
-
-    protected <T, S extends M> ModelView<? extends T> toView(ModelRuleDescriptor sourceDescriptor, MutableModelNode node, Class<S> itemClass) {
-        ModelType<S> itemType = ModelType.of(itemClass);
-        CollectionBuilder<M> builder = new DefaultCollectionBuilder<M>(baseItemModelType, sourceDescriptor, node, DefaultCollectionBuilder.createAndStoreVia(instantiatorModelReference, storeReference));
-
-        CollectionBuilder<S> subBuilder = builder.withType(itemClass);
-        CollectionBuilderModelView<S> view = new CollectionBuilderModelView<S>(node.getPath(), DefaultCollectionBuilder.typeOf(itemType), subBuilder, sourceDescriptor);
-        @SuppressWarnings("unchecked") ModelView<T> cast = (ModelView<T>) view;
-        return cast;
-    }
-
-    public static String getBuilderTypeDescriptionForCreatableTypes(Collection<? extends Class<?>> createableTypes) {
-        StringBuilder sb = new StringBuilder(CollectionBuilder.class.getName());
-        if (createableTypes.size() == 1) {
-            @SuppressWarnings("ConstantConditions")
-            String onlyType = Iterables.getFirst(createableTypes, null).getName();
-            sb.append("<").append(onlyType).append(">");
-        } else {
-            sb.append("<T>; where T is one of [");
-            Joiner.on(", ").appendTo(sb, CollectionUtils.sort(Iterables.transform(createableTypes, new Function<Class<?>, String>() {
-                public String apply(Class<?> input) {
-                    return input.getName();
-                }
-            })));
-            sb.append("]");
-        }
-        return sb.toString();
-    }
-
-    public <T> boolean canBeViewedAsWritable(ModelType<T> targetType) {
-        return itemType(targetType) != null;
-    }
-
-    protected Class<? extends M> itemType(ModelType<?> targetType) {
-        Class<?> targetClass = targetType.getRawClass();
-        if (targetClass.equals(CollectionBuilder.class)) {
-            Class<?> targetItemClass = targetType.getTypeVariables().get(0).getRawClass();
-            if (targetItemClass.isAssignableFrom(baseItemType)) {
-                return baseItemType;
-            }
-            if (baseItemType.isAssignableFrom(targetItemClass)) {
-                return targetItemClass.asSubclass(baseItemType);
-            }
-            return null;
-        }
-        if (targetClass.isAssignableFrom(CollectionBuilder.class)) {
-            return baseItemType;
-        }
-        return null;
-    }
-
-    public <T> boolean canBeViewedAsReadOnly(ModelType<T> type) {
-        return canBeViewedAsWritable(type);
-    }
-
-    public <T> ModelView<? extends T> asReadOnly(ModelType<T> type, MutableModelNode modelNode, @Nullable ModelRuleDescriptor ruleDescriptor) {
-        return asWritable(type, modelNode, ruleDescriptor, null);
-    }
-
-    public Iterable<String> getReadableTypeDescriptions() {
-        return getWritableTypeDescriptions();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DomainObjectContainerModelProjection that = (DomainObjectContainerModelProjection) o;
-
-        return baseItemType.equals(that.baseItemType) && instantiatorModelReference.equals(that.instantiatorModelReference);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = baseItemType.hashCode();
-        result = 31 * result + instantiatorModelReference.hashCode();
-        return result;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DynamicTypesDomainObjectContainerModelProjection.java b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DynamicTypesDomainObjectContainerModelProjection.java
deleted file mode 100644
index c1cabe1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/DynamicTypesDomainObjectContainerModelProjection.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.model.collection.internal;
-
-import org.gradle.api.internal.PolymorphicDomainObjectContainerInternal;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.core.NamedEntityInstantiator;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * When reporting its writable views, considers what factories have been registered.
- *
- * @see StaticTypeDomainObjectContainerModelProjection
- */
-public class DynamicTypesDomainObjectContainerModelProjection<C extends PolymorphicDomainObjectContainerInternal<M>, M> extends DomainObjectContainerModelProjection<C, M> {
-
-    private final C container;
-
-    public DynamicTypesDomainObjectContainerModelProjection(C container, ModelType<M> itemType, ModelReference<NamedEntityInstantiator<M>> instantiatorModelReference, ModelReference<? extends Collection<? super M>> storeReference) {
-        super(itemType, instantiatorModelReference, storeReference);
-        this.container = container;
-    }
-
-    public Iterable<String> getWritableTypeDescriptions() {
-        return Collections.singleton(getBuilderTypeDescriptionForCreatableTypes(container.getCreateableTypes()));
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/PolymorphicModelMapProjection.java b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/PolymorphicModelMapProjection.java
new file mode 100644
index 0000000..83026eb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/PolymorphicModelMapProjection.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.collection.internal;
+
+import org.gradle.api.internal.PolymorphicNamedEntityInstantiator;
+import org.gradle.model.internal.core.ChildNodeCreatorStrategy;
+import org.gradle.model.internal.core.ModelProjection;
+import org.gradle.model.internal.core.MutableModelNode;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collection;
+
+public class PolymorphicModelMapProjection<T> extends ModelMapModelProjection<T> {
+
+    public static <T> ModelProjection ofEager(ModelType<T> itemType, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        return new PolymorphicModelMapProjection<T>(itemType, true, creatorStrategy);
+    }
+
+    public static <T> ModelProjection of(ModelType<T> itemType, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        return new PolymorphicModelMapProjection<T>(itemType, false, creatorStrategy);
+    }
+
+    private PolymorphicModelMapProjection(ModelType<T> baseItemType, boolean eager, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        super(baseItemType, eager, false, creatorStrategy);
+    }
+
+    @Override
+    protected Collection<? extends Class<?>> getCreatableTypes(MutableModelNode node) {
+        ModelType<PolymorphicNamedEntityInstantiator<T>> instantiatorType = new ModelType.Builder<PolymorphicNamedEntityInstantiator<T>>() {
+        }.where(new ModelType.Parameter<T>() {
+        }, baseItemModelType).build();
+
+        PolymorphicNamedEntityInstantiator<T> instantiator = node.getPrivateData(instantiatorType);
+        return instantiator.getCreatableTypes();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/StaticTypeDomainObjectContainerModelProjection.java b/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/StaticTypeDomainObjectContainerModelProjection.java
deleted file mode 100644
index b18512e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/model/collection/internal/StaticTypeDomainObjectContainerModelProjection.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.model.collection.internal;
-
-import org.gradle.api.internal.PolymorphicDomainObjectContainerInternal;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.core.NamedEntityInstantiator;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Collection;
-import java.util.Collections;
-
-public class StaticTypeDomainObjectContainerModelProjection<C extends PolymorphicDomainObjectContainerInternal<M>, M> extends DomainObjectContainerModelProjection<C, M> {
-
-    private final ModelType<C> collectionType;
-
-    public StaticTypeDomainObjectContainerModelProjection(ModelType<C> collectionType, ModelType<M> itemType, ModelReference<NamedEntityInstantiator<M>> instantiatorModelReference, ModelReference<? extends Collection<? super M>> storeReference) {
-        super(itemType, instantiatorModelReference, storeReference);
-        this.collectionType = collectionType;
-    }
-
-    @Override
-    public Iterable<String> getWritableTypeDescriptions() {
-        return Collections.singleton(getBuilderTypeDescriptionForCreatableTypes(Collections.singleton(baseItemType)));
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        if (!super.equals(o)) {
-            return false;
-        }
-
-        StaticTypeDomainObjectContainerModelProjection<?, ?> that = (StaticTypeDomainObjectContainerModelProjection<?, ?>) o;
-
-        return collectionType.equals(that.collectionType);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = super.hashCode();
-        result = 31 * result + collectionType.hashCode();
-        return result;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/plugin/use/internal/PluginRequestApplicator.java b/subprojects/core/src/main/groovy/org/gradle/plugin/use/internal/PluginRequestApplicator.java
index 0f2fe74..75ff142 100644
--- a/subprojects/core/src/main/groovy/org/gradle/plugin/use/internal/PluginRequestApplicator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/plugin/use/internal/PluginRequestApplicator.java
@@ -16,11 +16,11 @@
 
 package org.gradle.plugin.use.internal;
 
-import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerInternal;
 import org.gradle.api.internal.plugins.PluginManagerInternal;
 
 // Implementation is provided by 'plugin-use' module
 public interface PluginRequestApplicator {
-    void applyPlugins(PluginRequests requests, ScriptHandler scriptHandler, PluginManagerInternal target, ClassLoaderScope classLoaderScope);
+    void applyPlugins(PluginRequests requests, ScriptHandlerInternal scriptHandler, PluginManagerInternal target, ClassLoaderScope classLoaderScope);
 }
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 c55fc07..13340c7 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
@@ -78,6 +78,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
     private final StreamsHandler streamsHandler;
     private final boolean redirectErrorStream;
     private final ProcessLauncher processLauncher;
+    private final DefaultExecutorFactory executorFactory = new DefaultExecutorFactory();
     private int timeoutMillis;
     private boolean daemon;
 
@@ -121,7 +122,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
         this.lock = new ReentrantLock();
         this.condition = lock.newCondition();
         this.state = ExecHandleState.INIT;
-        executor = new DefaultExecutorFactory().create(String.format("Run %s", displayName));
+        executor = executorFactory.create(String.format("Run %s", displayName));
         processLauncher = NativeServices.getInstance().get(ProcessLauncher.class);
         shutdownHookAction = new ExecHandleShutdownHookAction(this);
         broadcast = new ListenerBroadcast<ExecHandleListener>(ExecHandleListener.class);
@@ -228,7 +229,7 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
             }
             setState(ExecHandleState.STARTING);
 
-            execHandleRunner = new ExecHandleRunner(this, streamsHandler, processLauncher);
+            execHandleRunner = new ExecHandleRunner(this, streamsHandler, processLauncher, executorFactory);
             executor.execute(execHandleRunner);
 
             while(stateIn(ExecHandleState.STARTING)) {
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 10024f0..b026872 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
@@ -19,6 +19,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.internal.concurrent.ExecutorFactory;
 import org.gradle.process.internal.streams.StreamsHandler;
 
 import java.util.concurrent.locks.Lock;
@@ -31,13 +32,15 @@ public class ExecHandleRunner implements Runnable {
     private final DefaultExecHandle execHandle;
     private final Lock lock = new ReentrantLock();
     private final ProcessLauncher processLauncher;
+    private final ExecutorFactory executorFactory;
 
     private Process process;
     private boolean aborted;
     private final StreamsHandler streamsHandler;
 
-    public ExecHandleRunner(DefaultExecHandle execHandle, StreamsHandler streamsHandler, ProcessLauncher processLauncher) {
+    public ExecHandleRunner(DefaultExecHandle execHandle, StreamsHandler streamsHandler, ProcessLauncher processLauncher, ExecutorFactory executorFactory) {
         this.processLauncher = processLauncher;
+        this.executorFactory = executorFactory;
         if (execHandle == null) {
             throw new IllegalArgumentException("execHandle == null!");
         }
@@ -63,7 +66,7 @@ public class ExecHandleRunner implements Runnable {
         try {
             ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
             Process process = processLauncher.start(processBuilder);
-            streamsHandler.connectStreams(process, execHandle.getDisplayName());
+            streamsHandler.connectStreams(process, execHandle.getDisplayName(), executorFactory);
             setProcess(process);
 
             execHandle.started();
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 35efc00..32e773a 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
@@ -16,7 +16,7 @@
 
 package org.gradle.process.internal.streams;
 
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.util.DisconnectableInputStream;
 
@@ -43,7 +43,7 @@ public class StreamsForwarder implements StreamsHandler {
         this.readErrorStream = readErrorStream;
     }
 
-    public void connectStreams(Process process, String processName) {
+    public void connectStreams(Process process, String processName, ExecutorFactory executorFactory) {
         /*
             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
@@ -58,7 +58,7 @@ public class StreamsForwarder implements StreamsHandler {
         standardInputRunner = new ExecOutputHandleRunner("write standard input into: " + processName,
                 instr, process.getOutputStream());
 
-        this.executor = new DefaultExecutorFactory().create(String.format("Forward streams with process: %s", processName));
+        this.executor = executorFactory.create(String.format("Forward streams with process: %s", processName));
     }
 
     public void start() {
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 204fa25..98f18bb 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,12 @@
 
 package org.gradle.process.internal.streams;
 
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.Stoppable;
 
 public interface StreamsHandler extends Stoppable {
 
     void start();
 
-    void connectStreams(Process process, String processName);
+    void connectStreams(Process process, String processName, ExecutorFactory executorFactory);
 }
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
index d7c62e4..dc5aad9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestBuildScopeServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestBuildScopeServices.java
@@ -20,7 +20,7 @@ import org.gradle.api.internal.GradleDistributionLocator;
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.initialization.FixedBuildCancellationToken;
+import org.gradle.initialization.DefaultBuildCancellationToken;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.scopes.BuildScopeServices;
 
@@ -35,7 +35,7 @@ public class TestBuildScopeServices extends BuildScopeServices {
     }
 
     protected BuildCancellationToken createBuildCancellationToken() {
-        return new FixedBuildCancellationToken();
+        return new DefaultBuildCancellationToken();
     }
 
     protected BuildClientMetaData createClientMetaData() {
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
index ec124a5..2b41443 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/file/ProjectCopySpecTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/file/ProjectCopySpecTest.groovy
@@ -20,6 +20,7 @@ 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.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -41,7 +42,7 @@ class ProjectCopySpecTest extends Specification {
         testDirectoryProvider.testDirectory.createDir("dest")
     }
 
-
+    @LeaksFileHandles
     def "copy spec is enhanced"() {
         given:
         def copySpecRootCalled = false
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiatorTest.groovy
new file mode 100644
index 0000000..2204ec3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicNamedEntityInstantiatorTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal
+
+import org.gradle.api.GradleException
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+import spock.lang.Subject
+import spock.lang.Unroll
+
+class DefaultPolymorphicNamedEntityInstantiatorTest extends Specification {
+
+    @Subject
+    def instantiator = new DefaultPolymorphicNamedEntityInstantiator<Base>(Base, "this container")
+
+    class Base {
+        String value
+    }
+
+    class TestType extends Base {}
+
+    class AnotherTestType extends Base {}
+
+    def "trying to create an entity for which there is no factory registered results in an exception"() {
+        given:
+        instantiator.registerFactory(TestType, {})
+
+        when:
+        instantiator.create("foo", Integer)
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot create a Integer because this type is not known to this container. Known types are: TestType"
+        e.cause instanceof NoFactoryRegisteredForTypeException
+    }
+
+    @Unroll
+    def "can retrieve all creatable types and supported type names"() {
+        when:
+        types.each { instantiator.registerFactory(it, {}) }
+
+        then:
+        instantiator.creatableTypes == types as Set
+        instantiator.supportedTypeNames == names
+
+        where:
+        types                       | names
+        []                          | "(None)"
+        [TestType]                  | "TestType"
+        [TestType, AnotherTestType] | "AnotherTestType, TestType"
+    }
+
+    def "can create a type for which a factory has been registered"() {
+        given:
+        instantiator.registerFactory(TestType, { new TestType(value: it) })
+
+        expect:
+        instantiator.create("foo", TestType).value == "foo"
+    }
+
+    def "registering an incompatible type results in an exception"() {
+        when:
+        instantiator.registerFactory(String, {})
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Cannot register a factory for type String because it is not a subtype of container element type Base."
+    }
+
+    def "registering factory for the same type more than once results in an exception"() {
+        given:
+        instantiator.registerFactory(TestType, {})
+
+        when:
+        instantiator.registerFactory(TestType, {})
+
+        then:
+        GradleException e = thrown()
+        e.message == "Cannot register a factory for type TestType because a factory for this type is already registered."
+    }
+
+    def "copying factories from a different instantiator"() {
+        given:
+        def source = new DefaultPolymorphicNamedEntityInstantiator<Base>(Base, null)
+        def factoryTypes = [TestType, AnotherTestType] as Set
+        factoryTypes.each { source.registerFactory(it, {}) }
+
+        when:
+        instantiator.copyFactoriesFrom(source)
+
+        then:
+        instantiator.creatableTypes == factoryTypes
+    }
+}
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 cdd7523..ded93c4 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
@@ -255,9 +255,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         def actionExecuted = false
         def closureAction = { t -> actionExecuted = true } as Action
         defaultTask.actions.add(closureAction)
-        defaultTask.execute()
+        execute(defaultTask)
         assertTrue(actionExecuted)
-
     }
 
     @Issue("GRADLE-2774")
@@ -266,7 +265,7 @@ class DefaultTaskTest extends AbstractTaskTest {
         def actionExecuted = false
         def closureAction = { t -> actionExecuted = true } as Action
         defaultTask.actions.addAll(Lists.newArrayList(closureAction))
-        defaultTask.execute()
+        execute(defaultTask)
 
         assertTrue(actionExecuted)
     }
@@ -277,9 +276,8 @@ class DefaultTaskTest extends AbstractTaskTest {
         def actionExecuted = false
         def closureAction = { t -> actionExecuted = true } as Action
         defaultTask.actions.addAll(0, Lists.newArrayList(closureAction))
-        defaultTask.execute()
+        execute(defaultTask)
         assertTrue(actionExecuted)
-
     }
 
     @Issue("GRADLE-2774")
@@ -288,7 +286,7 @@ class DefaultTaskTest extends AbstractTaskTest {
         def actionExecuted = false
         def closureAction = { t -> actionExecuted = true } as Action
         defaultTask.actions.listIterator().add(closureAction)
-        defaultTask.execute()
+        execute(defaultTask)
         assertTrue(actionExecuted)
     }
 
@@ -350,17 +348,6 @@ class DefaultTaskTest extends AbstractTaskTest {
     }
 
     @Test
-    public void testExecuteWithoutThrowingTaskFailureThrowsExecutionFailure() {
-        def failure = new RuntimeException()
-        defaultTask.doFirst { throw failure }
-
-        defaultTask.executeWithoutThrowingTaskFailure()
-
-        assertThat(defaultTask.state.failure, instanceOf(TaskExecutionException))
-        assertThat(defaultTask.state.failure.cause, sameInstance(failure))
-    }
-
-    @Test
     void getAndSetConventionProperties() {
         TestConvention convention = new TestConvention()
         defaultTask.convention.plugins.test = convention
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 bc1b489..530d318 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
@@ -107,7 +107,7 @@ class DefaultProjectDependencyTest extends Specification {
         0 * _
     }
 
-    void "is Buildable"() {
+    void "is buildable"() {
         def context = Mock(TaskDependencyResolveContext)
 
         def conf = project.configurations.create('conf')
@@ -125,15 +125,30 @@ class DefaultProjectDependencyTest extends Specification {
     }
 
     void "does not build project dependencies if configured so"() {
-        def context = Mock(TaskDependencyResolveContext)
-        project.configurations.create('conf')
-        projectDependency = new DefaultProjectDependency(project, 'conf', listener, false)
+         def context = Mock(TaskDependencyResolveContext)
+         project.configurations.create('conf')
+         projectDependency = new DefaultProjectDependency(project, 'conf', listener, false)
+
+         when:
+         projectDependency.buildDependencies.resolve(context)
+
+         then:
+         0 * _
+     }
+
+    void "is self resolving dependency"() {
+        def conf = project.configurations.create('conf')
+        def listener = Mock(ProjectAccessListener)
+        projectDependency = new DefaultProjectDependency(project, 'conf', listener, true)
 
         when:
-        projectDependency.buildDependencies.resolve(context)
+        def files = projectDependency.resolve()
 
         then:
         0 * _
+
+        and:
+        files == conf.allArtifacts.files as Set
     }
 
     void "knows when content is equal"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java
index 80f9b66..76e8a47 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java
@@ -17,24 +17,26 @@ package org.gradle.api.internal.artifacts.dependencies;
 
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.SelfResolvingDependency;
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.tasks.TaskDependency;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+import static org.gradle.util.WrapUtil.toLinkedSet;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 @RunWith(JMock.class)
 public class DefaultSelfResolvingDependencyTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final FileCollection source = context.mock(FileCollection.class);
+    private final FileCollectionInternal source = context.mock(FileCollectionInternal.class);
     private final DefaultSelfResolvingDependency dependency = new DefaultSelfResolvingDependency(source);
 
     @Test
@@ -54,7 +56,7 @@ public class DefaultSelfResolvingDependencyTest {
 
         dependency.resolve(resolveContext);
     }
-    
+
     @Test
     public void usesSourceFileCollectionToResolveFiles() {
         final File file = new File("file");
@@ -80,7 +82,7 @@ public class DefaultSelfResolvingDependencyTest {
     @Test
     public void contentsAreEqualWhenFileSetsAreEqual() {
         SelfResolvingDependency equalDependency = new DefaultSelfResolvingDependency(source);
-        SelfResolvingDependency differentSource = new DefaultSelfResolvingDependency(context.mock(FileCollection.class, "other"));
+        SelfResolvingDependency differentSource = new DefaultSelfResolvingDependency(context.mock(FileCollectionInternal.class, "other"));
         Dependency differentType = context.mock(Dependency.class);
 
         assertTrue(dependency.contentEquals(dependency));
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 493c4fc..8391b93 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
@@ -26,8 +26,8 @@ import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 import org.gradle.util.GUtil;
-import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.TestUtil;
 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/api/internal/file/BaseDirFileResolverSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
index 2ab50ab..dddea94 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
@@ -163,7 +163,7 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.WINDOWS)
     def "normalizes non-existent file system root"() {
-        def file = new File("Q:\\")
+        def file = nonexistentFsRoot()
         assert !file.exists()
         assert file.absolute
 
@@ -217,4 +217,12 @@ The following types/formats are supported:
     private File[] getFsRoots() {
         File.listRoots().findAll { !it.absolutePath.startsWith("A:") }
     }
+    
+    private File nonexistentFsRoot() {
+        ('Z'..'A').collect { 
+            "$it:\\" 
+        }.findResult {
+            new File(it).exists() ? null : new File(it)
+        }
+    }
 }
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 2fb77ad..30dbb1d 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
@@ -18,12 +18,14 @@ package org.gradle.api.internal.file;
 import org.gradle.api.Task;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.file.collections.*;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
+import org.gradle.api.internal.file.collections.MinimalFileSet;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
-import org.gradle.util.TestUtil;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.TestUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -111,7 +113,7 @@ public class CompositeFileCollectionTest {
 
         assertFalse(collection.contains(file1));
     }
-    
+
     @Test
     public void isEmptyWhenHasNoSets() {
         CompositeFileCollection set = new TestCompositeFileCollection();
@@ -163,7 +165,7 @@ public class CompositeFileCollectionTest {
         }});
         assertThat(collection.getAsFileTrees(), equalTo((Collection) toList(set1, set2)));
     }
-    
+
     @Test
     public void getAsFileTreeDelegatesToEachSet() {
         final File file1 = new File("dir1");
@@ -217,8 +219,8 @@ public class CompositeFileCollectionTest {
 
     @Test
     public void filterDelegatesToEachSet() {
-        final FileCollection filtered1 = context.mock(FileCollection.class);
-        final FileCollection filtered2 = context.mock(FileCollection.class);
+        final FileCollectionInternal filtered1 = context.mock(FileCollectionInternal.class);
+        final FileCollectionInternal filtered2 = context.mock(FileCollectionInternal.class);
         @SuppressWarnings("unchecked")
         final Spec<File> spec = context.mock(Spec.class);
 
@@ -243,7 +245,7 @@ public class CompositeFileCollectionTest {
         final Task task3 = context.mock(Task.class, "task3");
         final FileCollection source3 = context.mock(FileCollection.class, "source3");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             TaskDependency dependency1 = context.mock(TaskDependency.class, "dep1");
             TaskDependency dependency2 = context.mock(TaskDependency.class, "dep2");
             TaskDependency dependency3 = context.mock(TaskDependency.class, "dep3");
@@ -286,7 +288,7 @@ public class CompositeFileCollectionTest {
         final Task task1 = context.mock(Task.class, "task1");
         final Task task2 = context.mock(Task.class, "task2");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             TaskDependency dependency1 = context.mock(TaskDependency.class, "dep1");
             TaskDependency dependency2 = context.mock(TaskDependency.class, "dep2");
 
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 59c4542..79e5f79 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
@@ -38,8 +38,8 @@ import org.junit.runner.RunWith;
 @RunWith(JMock.class)
 public class CompositeFileTreeTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final FileTree source1 = context.mock(FileTree.class);
-    private final FileTree source2 = context.mock(FileTree.class);
+    private final FileTreeInternal source1 = context.mock(FileTreeInternal.class);
+    private final FileTreeInternal source2 = context.mock(FileTreeInternal.class);
     private final CompositeFileTree tree = new CompositeFileTree() {
         @Override
         public String getDisplayName() {
@@ -61,8 +61,8 @@ public class CompositeFileTreeTest {
     @Test
     public void matchingWithClosureReturnsUnionOfFilteredSets() {
         final Closure closure = TestUtil.TEST_CLOSURE;
-        final FileTree filtered1 = context.mock(FileTree.class);
-        final FileTree filtered2 = context.mock(FileTree.class);
+        final FileTreeInternal filtered1 = context.mock(FileTreeInternal.class);
+        final FileTreeInternal filtered2 = context.mock(FileTreeInternal.class);
 
         context.checking(new Expectations() {{
             one(source1).matching(closure);
@@ -75,14 +75,14 @@ public class CompositeFileTreeTest {
         assertThat(filtered, instanceOf(CompositeFileTree.class));
         CompositeFileTree filteredCompositeSet = (CompositeFileTree) filtered;
 
-        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList((FileTree)filtered1, filtered2)));
+        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList(filtered1, filtered2)));
     }
 
     @Test
     public void matchingWithPatternSetReturnsUnionOfFilteredSets() {
         final PatternSet patternSet = new PatternSet();
-        final FileTree filtered1 = context.mock(FileTree.class);
-        final FileTree filtered2 = context.mock(FileTree.class);
+        final FileTreeInternal filtered1 = context.mock(FileTreeInternal.class);
+        final FileTreeInternal filtered2 = context.mock(FileTreeInternal.class);
 
         context.checking(new Expectations() {{
             one(source1).matching(patternSet);
@@ -95,12 +95,12 @@ public class CompositeFileTreeTest {
         assertThat(filtered, instanceOf(CompositeFileTree.class));
         CompositeFileTree filteredCompositeSet = (CompositeFileTree) filtered;
 
-        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList((FileTree) filtered1, filtered2)));
+        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList(filtered1, filtered2)));
     }
 
     @Test
     public void plusReturnsUnionOfThisTreeAndSourceTree() {
-        FileTree other = context.mock(FileTree.class);
+        FileTreeInternal other = context.mock(FileTreeInternal.class);
 
         FileTree sum = tree.plus(other);
         assertThat(sum, instanceOf(CompositeFileTree.class));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
index 6c33076..72cb2e6 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
@@ -131,7 +131,7 @@ public class DefaultFileOperationsTest extends Specification {
 
     def createsTarFileTree() {
         TestFile file = tmpDir.file('path')
-        resolver.resolveResource('path') >> new FileResource(file)
+        resolver.resolve('path') >> file
 
         when:
         def tarTree = fileOperations.tarTree('path')
@@ -142,7 +142,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def copiesFiles() {
-        FileTree fileTree = Mock(FileTree)
+        def fileTree = Mock(FileTreeInternal)
         resolver.resolveFilesAsTree(_) >> fileTree
         // todo we should make this work so that we can be more specific
 //        resolver.resolveFilesAsTree(['file'] as Object[]) >> fileTree
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
index 073e9fe..4a08299 100644
--- 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
@@ -17,17 +17,15 @@ 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()
+    FileCollectionInternal delegatedTo = Mock()
     DelegatingFileCollection fileCollection = new DelegatingFileCollection() {
         @Override
-        FileCollection getDelegate() {
+        FileCollectionInternal getDelegate() {
             delegatedTo
         }
     }
@@ -55,6 +53,7 @@ class DelegatingFileCollectionTest extends Specification {
             addToAntBuilder(anObject, "nodeName", FileCollection.AntType.MatchingTask)
             addToAntBuilder(anObject, "nodeName")
             getBuildDependencies()
+            getDisplayName()
             delegate.iterator() // avoid collision with DGM method
         }
 
@@ -75,30 +74,11 @@ class DelegatingFileCollectionTest extends Specification {
             1 * getAsFileTree()
             1 * addToAntBuilder(anObject, "nodeName", FileCollection.AntType.MatchingTask)
             1 * addToAntBuilder(anObject, "nodeName")
+            1 * getDisplayName()
             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/FileSystemSubsetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileSystemSubsetTest.groovy
new file mode 100644
index 0000000..4296964
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileSystemSubsetTest.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file
+
+import org.gradle.api.file.FileTreeElement
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.util.PatternSet
+import org.gradle.util.UsesNativeServices
+import spock.lang.Specification
+
+ at UsesNativeServices
+class FileSystemSubsetTest extends Specification {
+
+    def "root of directory tree is contained"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder().add(f).build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "sub"))
+        !s.contains(f.parentFile)
+    }
+
+    def "can be empty"() {
+        expect:
+        FileSystemSubset.builder().build().empty
+        !FileSystemSubset.builder().add(new File("f")).build().empty
+    }
+
+    def "can filter tree"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder().add(f, new PatternSet().include("*.txt")).build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        !s.contains(new File(f, "some.img"))
+    }
+
+    def "can compose"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder()
+            .add(f, new PatternSet().include("*.txt"))
+            .add(f)
+            .build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        s.contains(new File(f, "some.img"))
+    }
+
+    def "can filter trees with the same directory but different include patterns"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder()
+            .add(f, new PatternSet().include("*.txt"))
+            .add(f, new PatternSet().include("*.jar"))
+            .build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        s.contains(new File(f, "some.jar"))
+        !s.contains(new File(f, "some.img"))
+    }
+
+    def "can filter trees with the same directory but different exclude patterns"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder()
+            .add(f, new PatternSet().exclude("*.txt"))
+            .add(f, new PatternSet().exclude("*.jar"))
+            .build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        s.contains(new File(f, "some.jar"))
+        s.contains(new File(f, "some.img"))
+    }
+
+    def "can filter trees with the same directory but different include specs"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder()
+            .add(f, new PatternSet().include(getExtensionSpec("txt")))
+            .add(f, new PatternSet().include(getExtensionSpec("jar")))
+            .build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        s.contains(new File(f, "some.jar"))
+        !s.contains(new File(f, "some.img"))
+    }
+
+    def "can filter trees with the same directory but different exclude specs"() {
+        when:
+        def f = new File('foo').absoluteFile
+        def s = FileSystemSubset.builder()
+            .add(f, new PatternSet().exclude(getExtensionSpec("txt")))
+            .add(f, new PatternSet().exclude(getExtensionSpec("jar")))
+            .build()
+
+        then:
+        s.contains(f)
+        s.contains(new File(f, "some.txt"))
+        s.contains(new File(f, "some.jar"))
+        s.contains(new File(f, "some.img"))
+    }
+
+    Spec<FileTreeElement> getExtensionSpec(String extension) {
+        return new Spec<FileTreeElement>() {
+            @Override
+            boolean isSatisfiedBy(FileTreeElement element) {
+                return element.file.name.endsWith(".${extension}")
+            }
+        }
+    }
+}
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
index c5734a2..09157a7 100644
--- 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
@@ -15,17 +15,15 @@
  */
 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() {
+        FileCollectionInternal createDelegate() {
             createCount++
             new SimpleFileCollection([new File("foo")])
         }
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 d3a886b..aac5ae5 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
@@ -20,16 +20,42 @@ package org.gradle.api.internal.file;
 import org.gradle.api.internal.file.archive.compression.Bzip2Archiver
 import org.gradle.api.internal.file.archive.compression.GzipArchiver
 import spock.lang.Specification
+import spock.lang.Unroll
 
 public class MaybeCompressedFileResourceTest extends Specification {
 
     def "understands file extensions"() {
         expect:
-        new MaybeCompressedFileResource(new FileResource(new File("foo"))).resource instanceof FileResource
-        new MaybeCompressedFileResource(new FileResource(new File("foo.tgz"))).resource instanceof GzipArchiver
-        new MaybeCompressedFileResource(new FileResource(new File("foo.gz"))).resource instanceof GzipArchiver
-        new MaybeCompressedFileResource(new FileResource(new File("foo.bz2"))).resource instanceof Bzip2Archiver
-        new MaybeCompressedFileResource(new FileResource(new File("foo.tbz2"))).resource instanceof Bzip2Archiver
+        new MaybeCompressedFileResource(fileResource("foo")).resource instanceof FileResource
+        new MaybeCompressedFileResource(fileResource("foo.tgz")).resource instanceof GzipArchiver
+        new MaybeCompressedFileResource(fileResource("foo.gz")).resource instanceof GzipArchiver
+        new MaybeCompressedFileResource(fileResource("foo.bz2")).resource instanceof Bzip2Archiver
+        new MaybeCompressedFileResource(fileResource("foo.tbz2")).resource instanceof Bzip2Archiver
+    }
+
+    @Unroll
+    def "passes through GzipArchiver resources called #name"() {
+        given:
+        def maybeCompressed = new MaybeCompressedFileResource(new GzipArchiver(fileResource(name)))
+        expect:
+        maybeCompressed.resource instanceof GzipArchiver
+        ((GzipArchiver)maybeCompressed.resource).resource instanceof FileResource
+        where:
+        name << [ "foo", "foo.tgz", "foo.gz" ]
+    }
+
+    @Unroll
+    def "passes through Bzip2Archiver resources called #name"() {
+        given:
+        def maybeCompressed = new MaybeCompressedFileResource(new Bzip2Archiver(fileResource(name)))
+        expect:
+        maybeCompressed.resource instanceof Bzip2Archiver
+        ((Bzip2Archiver)maybeCompressed.resource).resource instanceof FileResource
+        where:
+        name << [ "foo", "foo.bz2", "foo.tbz2" ]
+    }
 
+    def fileResource(String fileName) {
+        new FileResource(new File(fileName))
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java
index e581924..4bb9439 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.file;
 
-import org.gradle.api.file.FileCollection;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
@@ -33,8 +32,8 @@ import static org.junit.Assert.assertThat;
 @RunWith(JMock.class)
 public class UnionFileCollectionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final FileCollection source1 = context.mock(FileCollection.class, "source1");
-    private final FileCollection source2 = context.mock(FileCollection.class, "source2");
+    private final FileCollectionInternal source1 = context.mock(FileCollectionInternal.class, "source1");
+    private final FileCollectionInternal source2 = context.mock(FileCollectionInternal.class, "source2");
 
     @Before
     public void setup() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java
index 817ce74..c188bf2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -41,7 +40,7 @@ public class UnionFileTreeTest {
 
     @Test
     public void canAddFileTree() {
-        FileTree set1 = context.mock(FileTree.class, "set1");
+        FileTreeInternal set1 = context.mock(FileTreeInternal.class, "set1");
 
         set.add(set1);
         assertThat(set.getSourceCollections(), equalTo((Iterable) toList((Object) set1)));
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
index adc7861..549e917 100644
--- 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
@@ -97,7 +97,7 @@ public class TarCopyActionTest {
         expected.put("dir", 2);
         expected.put("file", 1);
 
-        assertVisitsPermissions(new TarFileTree(new FileResource(tarFile), null, fileSystem()),
+        assertVisitsPermissions(new TarFileTree(tarFile, new FileResource(tarFile), null, fileSystem()),
                 expected);
     }
 
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 48af36e..45b504f 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
@@ -43,7 +43,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, fileSystem());
+    private final TarFileTree tree = new TarFileTree(tarFile, new MaybeCompressedFileResource(new FileResource(tarFile)), expandDir, fileSystem());
 
     @Test
     public void displayName() {
@@ -68,7 +68,7 @@ public class TarFileTreeTest {
         rootDir.file("subdir2/file2.txt").write("content");
         rootDir.tgzTo(tgz);
 
-        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tgz)), expandDir, fileSystem());
+        TarFileTree tree = new TarFileTree(tarFile, 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 +82,7 @@ public class TarFileTreeTest {
         rootDir.file("subdir2/file2.txt").write("content");
         rootDir.tbzTo(tbz2);
 
-        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tbz2)), expandDir, fileSystem());
+        TarFileTree tree = new TarFileTree(tarFile, 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"));
@@ -143,4 +143,14 @@ public class TarFileTreeTest {
 
         assertVisitsPermissions(tree, expected);
     }
+
+    @Test
+    public void readsTarFileWithNullPermissions() {
+        resources.findResource("nullpermissions.tar").copyTo(tarFile);
+
+        final Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("bin", 0755);
+
+        assertVisitsPermissions(tree, expected);
+    }
 }
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 6fb3d95..eb7413b 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
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file.collections;
 import groovy.lang.Closure;
 import org.gradle.api.Task;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.tasks.TaskDependency;
@@ -218,7 +219,7 @@ public class DefaultConfigurableFileCollectionTest {
         final File file1 = new File("1");
         final File file2 = new File("2");
 
-        final FileCollection src = context.mock(FileCollection.class);
+        final FileCollectionInternal src = context.mock(FileCollectionInternal.class);
 
         collection.from(src);
 
@@ -371,5 +372,5 @@ public class DefaultConfigurableFileCollectionTest {
         assertThat(collection.getAsFileTree().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/DefaultFileCollectionResolveContextTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
index cfe6fe4..2cfc486 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
@@ -1,436 +1,435 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.UsesNativeServices
-
-import java.util.concurrent.Callable
-
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.tasks.TaskDependency
-import spock.lang.Specification
-import org.gradle.api.file.FileTree
-import org.gradle.api.file.FileCollection
-import org.gradle.api.Task
-import org.gradle.api.tasks.TaskOutputs
-
- at UsesNativeServices
-class DefaultFileCollectionResolveContextTest extends Specification {
-    final FileResolver resolver = Mock()
-    final DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext(resolver)
-
-    def resolveAsFileCollectionReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsFileCollections() == []
-    }
-
-    def resolveAsFileTreeReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsFileTrees() == []
-    }
-
-    def resolveAsMinimalFileCollectionReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsMinimalFileCollections() == []
-    }
-
-    def resolveAsFileCollectionWrapsAMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection == fileSet
-    }
-
-    def resolveAsFileTreeConvertsTheElementsOfMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-        File file = this.file('file1')
-        File dir = directory('file2')
-        File doesNotExist = nonExistent('file3')
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 2
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        result[1] instanceof FileTreeAdapter
-        result[1].tree instanceof DirectoryFileTree
-        result[1].tree.dir == dir
-        1 * fileSet.files >> ([file, dir, doesNotExist] as LinkedHashSet)
-    }
-
-    def resolveAsMinimalFileCollectionReturnsMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [fileSet]
-    }
-
-    def resolveAsFileCollectionWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree == fileTree
-    }
-
-    def resolveAsFileTreesWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree == fileTree
-    }
-
-    def resolveAsMinimalFileCollectionWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [fileTree]
-    }
-
-    def resolveAsFileCollectionsForAFileCollection() {
-        FileCollection fileCollection = Mock()
-
-        when:
-        context.add(fileCollection)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [fileCollection]
-    }
-
-    def resolveAsFileCollectionsDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        FileCollection contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolveAsFileTreesDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        FileTree contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolveAsMinimalFileCollectionsDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        MinimalFileCollection contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolvesCompositeFileCollectionsInDepthwiseOrder() {
-        FileCollectionContainer parent1 = Mock()
-        FileCollection child1 = Mock()
-        FileCollectionContainer parent2 = Mock()
-        FileCollection child2 = Mock()
-        FileCollection child3 = Mock()
-
-        when:
-        context.add(parent1)
-        context.add(child3)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [child1, child2, child3]
-        1 * parent1.resolve(!null) >> { it[0].add(child1); it[0].add(parent2) }
-        1 * parent2.resolve(!null) >> { it[0].add(child2) }
-    }
-
-    def recursivelyResolvesReturnValueOfAClosure() {
-        FileCollection content = Mock()
-
-        when:
-        context.add { content }
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [content]
-    }
-
-    def resolvesAClosureWhichReturnsNull() {
-        when:
-        context.add { null }
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == []
-    }
-
-    def resolvesTasksOutputsWithEmptyFileCollection() {
-        FileCollection content = Mock()
-        TaskOutputs outputs = Mock()
-        when:
-
-        context.add outputs
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * outputs.files >> content
-        result == [content]
-    }
-
-    def recursivelyResolvesReturnValueOfACallable() {
-        FileCollection content = Mock()
-        Callable<?> callable = Mock()
-
-        when:
-        context.add(callable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * callable.call() >> content
-        result == [content]
-    }
-
-    def resolvesACallableWhichReturnsNull() {
-        Callable<?> callable = Mock()
-
-        when:
-        context.add(callable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * callable.call() >> null
-        result == []
-    }
-
-    def recursivelyResolvesElementsOfAnIterable() {
-        FileCollection content = Mock()
-        Iterable<Object> iterable = Mock()
-
-        when:
-        context.add(iterable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * iterable.iterator() >> [content].iterator()
-        result == [content]
-    }
-
-    def recursivelyResolvesElementsAnArray() {
-        FileCollection content = Mock()
-
-        when:
-        context.add([content] as Object[])
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [content]
-    }
-
-    def resolveAsFileCollectionsIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == []
-    }
-
-    def resolveAsFileTreesIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result == []
-    }
-
-    def resolveAsMinimalFileCollectionsIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == []
-    }
-
-    def resolveAsFileCollectionsResolvesTaskToItsOutputFiles() {
-        Task task = Mock()
-        TaskOutputs outputs = Mock()
-        FileCollection outputFiles = Mock()
-
-        given:
-        _ * task.outputs >> outputs
-        _ * outputs.files >> outputFiles
-
-        when:
-        context.add(task)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [outputFiles]
-    }
-
-    def resolveAsFileCollectionsUsesFileResolverToResolveOtherTypes() {
-        File file1 = new File('a')
-        File file2 = new File('b')
-
-        when:
-        context.add('a')
-        context.add('b')
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 2
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection instanceof ListBackedFileSet
-        result[0].fileCollection.files as List == [file1]
-        result[1] instanceof FileCollectionAdapter
-        result[1].fileCollection instanceof ListBackedFileSet
-        result[1].fileCollection.files as List == [file2]
-        1 * resolver.resolve('a') >> file1
-        1 * resolver.resolve('b') >> file2
-    }
-
-    def resolveAsFileTreeUsesFileResolverToResolveOtherTypes() {
-        File file = file('a')
-
-        when:
-        context.add('a')
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        1 * resolver.resolve('a') >> file
-    }
-
-    def resolveAsMinimalFileCollectionUsesFileResolverToResolveOtherTypes() {
-        File file = file('a')
-
-        when:
-        context.add('a')
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof ListBackedFileSet
-        result[0].files as List == [file]
-        1 * resolver.resolve('a') >> file
-    }
-
-    def canPushContextWhichUsesADifferentFileResolverToConvertToFileCollections() {
-        FileResolver fileResolver = Mock()
-        File file = new File('a')
-
-        when:
-        context.push(fileResolver).add('a')
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection instanceof ListBackedFileSet
-        result[0].fileCollection.files as List == [file]
-        1 * fileResolver.resolve('a') >> file
-        0 * _._
-    }
-
-    def canPushContextWhichUsesADifferentFileResolverToConvertToFileTrees() {
-        FileResolver fileResolver = Mock()
-        File file = file('a')
-
-        when:
-        context.push(fileResolver).add('a')
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        1 * fileResolver.resolve('a') >> file
-    }
-
-    def file(String name) {
-        File f = Mock()
-        _ * f.file >> true
-        _ * f.exists() >> true
-        _ * f.canonicalFile >> f
-        f
-    }
-
-    def directory(String name) {
-        File f = Mock()
-        _ * f.directory >> true
-        _ * f.exists() >> true
-        _ * f.canonicalFile >> f
-        f
-    }
-
-    def nonExistent(String name) {
-        File f = Mock()
-        _ * f.canonicalFile >> f
-        f
-    }
-}
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Task
+import org.gradle.api.internal.file.FileCollectionInternal
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.FileTreeInternal
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.tasks.TaskOutputs
+import org.gradle.util.UsesNativeServices
+import spock.lang.Specification
+
+import java.util.concurrent.Callable
+
+ at UsesNativeServices
+class DefaultFileCollectionResolveContextTest extends Specification {
+    final FileResolver resolver = Mock()
+    final DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext(resolver)
+
+    def resolveAsFileCollectionReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsFileCollections() == []
+    }
+
+    def resolveAsFileTreeReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsFileTrees() == []
+    }
+
+    def resolveAsMinimalFileCollectionReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsMinimalFileCollections() == []
+    }
+
+    def resolveAsFileCollectionWrapsAMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection == fileSet
+    }
+
+    def resolveAsFileTreeConvertsTheElementsOfMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+        File file = this.file('file1')
+        File dir = directory('file2')
+        File doesNotExist = nonExistent('file3')
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 2
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        result[1] instanceof FileTreeAdapter
+        result[1].tree instanceof DirectoryFileTree
+        result[1].tree.dir == dir
+        1 * fileSet.files >> ([file, dir, doesNotExist] as LinkedHashSet)
+    }
+
+    def resolveAsMinimalFileCollectionReturnsMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [fileSet]
+    }
+
+    def resolveAsFileCollectionWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree == fileTree
+    }
+
+    def resolveAsFileTreesWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree == fileTree
+    }
+
+    def resolveAsMinimalFileCollectionWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [fileTree]
+    }
+
+    def resolveAsFileCollectionsForAFileCollection() {
+        FileCollectionInternal fileCollection = Mock()
+
+        when:
+        context.add(fileCollection)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [fileCollection]
+    }
+
+    def resolveAsFileCollectionsDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        FileCollectionInternal contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolveAsFileTreesDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        FileTreeInternal contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolveAsMinimalFileCollectionsDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        MinimalFileCollection contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolvesCompositeFileCollectionsInDepthwiseOrder() {
+        FileCollectionContainer parent1 = Mock()
+        FileCollectionInternal child1 = Mock()
+        FileCollectionContainer parent2 = Mock()
+        FileCollectionInternal child2 = Mock()
+        FileCollectionInternal child3 = Mock()
+
+        when:
+        context.add(parent1)
+        context.add(child3)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [child1, child2, child3]
+        1 * parent1.resolve(!null) >> { it[0].add(child1); it[0].add(parent2) }
+        1 * parent2.resolve(!null) >> { it[0].add(child2) }
+    }
+
+    def recursivelyResolvesReturnValueOfAClosure() {
+        FileCollectionInternal content = Mock()
+
+        when:
+        context.add { content }
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [content]
+    }
+
+    def resolvesAClosureWhichReturnsNull() {
+        when:
+        context.add { null }
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolvesTasksOutputsWithEmptyFileCollection() {
+        FileCollectionInternal content = Mock()
+        TaskOutputs outputs = Mock()
+        when:
+
+        context.add outputs
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * outputs.files >> content
+        result == [content]
+    }
+
+    def recursivelyResolvesReturnValueOfACallable() {
+        FileCollectionInternal content = Mock()
+        Callable<?> callable = Mock()
+
+        when:
+        context.add(callable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * callable.call() >> content
+        result == [content]
+    }
+
+    def resolvesACallableWhichReturnsNull() {
+        Callable<?> callable = Mock()
+
+        when:
+        context.add(callable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * callable.call() >> null
+        result == []
+    }
+
+    def recursivelyResolvesElementsOfAnIterable() {
+        FileCollectionInternal content = Mock()
+        Iterable<Object> iterable = Mock()
+
+        when:
+        context.add(iterable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * iterable.iterator() >> [content].iterator()
+        result == [content]
+    }
+
+    def recursivelyResolvesElementsAnArray() {
+        FileCollectionInternal content = Mock()
+
+        when:
+        context.add([content] as Object[])
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [content]
+    }
+
+    def resolveAsFileCollectionsIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolveAsFileTreesIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result == []
+    }
+
+    def resolveAsMinimalFileCollectionsIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolveAsFileCollectionsResolvesTaskToItsOutputFiles() {
+        Task task = Mock()
+        TaskOutputs outputs = Mock()
+        FileCollectionInternal outputFiles = Mock()
+
+        given:
+        _ * task.outputs >> outputs
+        _ * outputs.files >> outputFiles
+
+        when:
+        context.add(task)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [outputFiles]
+    }
+
+    def resolveAsFileCollectionsUsesFileResolverToResolveOtherTypes() {
+        File file1 = new File('a')
+        File file2 = new File('b')
+
+        when:
+        context.add('a')
+        context.add('b')
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 2
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection instanceof ListBackedFileSet
+        result[0].fileCollection.files as List == [file1]
+        result[1] instanceof FileCollectionAdapter
+        result[1].fileCollection instanceof ListBackedFileSet
+        result[1].fileCollection.files as List == [file2]
+        1 * resolver.resolve('a') >> file1
+        1 * resolver.resolve('b') >> file2
+    }
+
+    def resolveAsFileTreeUsesFileResolverToResolveOtherTypes() {
+        File file = file('a')
+
+        when:
+        context.add('a')
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        1 * resolver.resolve('a') >> file
+    }
+
+    def resolveAsMinimalFileCollectionUsesFileResolverToResolveOtherTypes() {
+        File file = file('a')
+
+        when:
+        context.add('a')
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof ListBackedFileSet
+        result[0].files as List == [file]
+        1 * resolver.resolve('a') >> file
+    }
+
+    def canPushContextWhichUsesADifferentFileResolverToConvertToFileCollections() {
+        FileResolver fileResolver = Mock()
+        File file = new File('a')
+
+        when:
+        context.push(fileResolver).add('a')
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection instanceof ListBackedFileSet
+        result[0].fileCollection.files as List == [file]
+        1 * fileResolver.resolve('a') >> file
+        0 * _._
+    }
+
+    def canPushContextWhichUsesADifferentFileResolverToConvertToFileTrees() {
+        FileResolver fileResolver = Mock()
+        File file = file('a')
+
+        when:
+        context.push(fileResolver).add('a')
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        1 * fileResolver.resolve('a') >> file
+    }
+
+    def file(String name) {
+        File f = Mock()
+        _ * f.file >> true
+        _ * f.exists() >> true
+        _ * f.canonicalFile >> f
+        f
+    }
+
+    def directory(String name) {
+        File f = Mock()
+        _ * f.directory >> true
+        _ * f.exists() >> true
+        _ * f.canonicalFile >> f
+        f
+    }
+
+    def nonExistent(String name) {
+        File f = Mock()
+        _ * f.canonicalFile >> f
+        f
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/FileTreeAdapterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/FileTreeAdapterTest.groovy
index 178e9fe..33bb4ca 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/FileTreeAdapterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/FileTreeAdapterTest.groovy
@@ -130,7 +130,7 @@ class FileTreeAdapterTest extends Specification {
         filteredAdapter.tree == filtered
         1 * tree.filter(filter) >> filtered
     }
-    
+
     def containsDelegatesToTargetTreeWhenItImplementsRandomAccessFileCollection() {
         TestFileTree tree = Mock()
         File f = new File('a')
@@ -146,4 +146,4 @@ class FileTreeAdapterTest extends Specification {
 }
 
 interface TestFileTree extends MinimalFileTree, Buildable, RandomAccessFileCollection {
-}
\ No newline at end of file
+}
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 d19bbf7..a3ac675 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
@@ -23,15 +23,18 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting;
 import static org.gradle.api.file.FileVisitorUtil.assertVisits;
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
 
 public class MapFileTreeTest {
     @Rule
@@ -45,7 +48,7 @@ public class MapFileTreeTest {
         assertVisits(tree, emptyList, emptyList);
         assertSetContainsForAllTypes(tree, emptyList);
     }
-    
+
     @Test
     public void canAddAnElementUsingAClosureToGeneratedContent() {
         Action<OutputStream> action = getAction();
@@ -77,6 +80,27 @@ public class MapFileTreeTest {
         assertCanStopVisiting(tree);
     }
 
+    @Test
+    public void containsWontCreateFiles() {
+        final AtomicInteger callCounter = new AtomicInteger(0);
+        Action<OutputStream> fileAction = new Action<OutputStream>() {
+            @Override
+            public void execute(OutputStream outputStream) {
+                callCounter.incrementAndGet();
+            }
+        };
+        tree.add("file.txt", fileAction);
+
+        FileTreeAdapter fileTreeAdapter = new FileTreeAdapter(tree);
+        File file = rootDir.file("file.txt");
+
+        assertTrue(fileTreeAdapter.contains(file));
+        assertTrue(fileTreeAdapter.contains(file));
+        assertFalse(fileTreeAdapter.contains(rootDir.file("file2.txt")));
+
+        assertEquals(0, callCounter.get());
+    }
+
     private Action<OutputStream> getAction() {
         return new Action<OutputStream>() {
             public void execute(OutputStream outputStream) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecResolutionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecResolutionTest.groovy
index 5aad73a..0324632 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecResolutionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DefaultCopySpecResolutionTest.groovy
@@ -18,9 +18,9 @@ package org.gradle.api.internal.file.copy
 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.internal.file.FileTreeInternal
 import org.gradle.api.specs.Spec
 import org.gradle.api.tasks.util.PatternSet
 import org.gradle.internal.reflect.DirectInstantiator
@@ -141,11 +141,11 @@ public class DefaultCopySpecResolutionTest {
         child.from 'b'
         DefaultCopySpec.DefaultCopySpecResolver childResolver = child.buildResolverRelativeToParent(parentSpec.buildRootResolver())
 
-        def filteredTree = context.mock(FileTree, 'filtered')
+        def filteredTree = context.mock(FileTreeInternal, 'filtered')
 
         context.checking {
             one(fileResolver).resolveFilesAsTree(['a', 'b'] as Set)
-            def tree = context.mock(FileTree, 'source')
+            def tree = context.mock(FileTreeInternal, 'source')
             will(returnValue(tree))
             one(tree).matching(withParam(equalTo(parentSpec.patternSet)))
             will(returnValue(filteredTree))
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
deleted file mode 100644
index b35df5b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.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.api.internal.initialization
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-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.DependencyMetaDataProvider
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.groovy.scripts.ScriptSource
-import spock.lang.Specification
-
-class DefaultScriptHandlerFactoryTest extends Specification {
-    private final DependencyMetaDataProvider metaDataProvider = Mock()
-    private final ClassLoaderScope parentScope = Stub() {
-        getLocalClassLoader() >> Stub(ClassLoader)
-    }
-    private final RepositoryHandler repositoryHandler = Mock()
-    private final ConfigurationContainer configurationContainer = Mock()
-    private final FileResolver fileResolver = Mock()
-    private final DependencyManagementServices dependencyManagementServices = Mock()
-    private final DefaultScriptHandlerFactory scriptHandlerFactory = new DefaultScriptHandlerFactory(dependencyManagementServices, fileResolver, metaDataProvider)
-
-    def createsScriptHandler() {
-        ScriptSource script = scriptSource()
-        expectConfigContainerCreated()
-
-        when:
-        def handler = scriptHandlerFactory.create(script, parentScope)
-
-        then:
-        handler instanceof DefaultScriptHandler
-    }
-
-    // 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')
-        ScriptSource other = scriptSource('other')
-        expectConfigContainerCreated()
-
-        when:
-        scriptHandlerFactory.create(script, parentScope)
-        def handler2 = scriptHandlerFactory.create(other, parentScope)
-
-        then:
-        handler2 instanceof DefaultScriptHandler
-    }
-
-    private def expectConfigContainerCreated() {
-        DependencyResolutionServices dependencyResolutionServices = Mock()
-        _ * dependencyManagementServices.create(fileResolver, metaDataProvider, _, _) >> dependencyResolutionServices
-        _ * dependencyResolutionServices.resolveRepositoryHandler >> repositoryHandler
-        _ * dependencyResolutionServices.configurationContainer >> configurationContainer
-        _ * configurationContainer.create(_) >> Stub(Configuration)
-    }
-
-    private def scriptSource(String className = 'script') {
-        ScriptSource script = Mock()
-        _ * script.className >> 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 9e93232..975854c 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
@@ -27,23 +27,62 @@ class DefaultScriptHandlerTest extends Specification {
     def repositoryHandler = Mock(RepositoryHandler)
     def dependencyHandler = Mock(DependencyHandler)
     def configurationContainer = Mock(ConfigurationContainer)
-    def configuration = Stub(Configuration)
+    def configuration = Mock(Configuration)
     def scriptSource = Stub(ScriptSource)
     def baseClassLoader = new ClassLoader() {}
     def classLoaderScope = Stub(ClassLoaderScope) {
         getLocalClassLoader() >> baseClassLoader
     }
+    def handler = new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoaderScope)
 
-    def "adds classpath configuration"() {
+    def "adds classpath configuration when configuration container is queried"() {
         when:
-        new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, new ScriptHandlerClassLoaderFactory(scriptSource, classLoaderScope))
+        handler.configurations
+        handler.configurations
 
         then:
-        1 * configurationContainer.create('classpath')
+        1 * configurationContainer.create('classpath') >> configuration
+        0 * configurationContainer._
+    }
+
+    def "adds classpath configuration when dependencies container is queried"() {
+        when:
+        handler.dependencies
+        handler.dependencies
+
+        then:
+        1 * configurationContainer.create('classpath') >> configuration
+        0 * configurationContainer._
+    }
+
+    def "does not resolve classpath configuration if configuration container has not been queried"() {
+        when:
+        def classpath = handler.scriptClassPath
+
+        then:
+        0 * configuration._
+
+        and:
+        classpath.empty
+    }
+
+    def "resolves classpath configuration if configuration container has been queried"() {
+        def file = new File("thing.jar")
+        def uri = file.toURI()
+
+        when:
+        handler.configurations
+        def classpath = handler.scriptClassPath
+
+        then:
+        1 * configurationContainer.create('classpath') >> configuration
+        1 * configuration.files >> [file]
+
+        and:
+        classpath.asURIs == [uri]
     }
 
     def "can configure repositories"() {
-        def handler = handler()
         def configure = {
             mavenCentral()
         }
@@ -57,8 +96,6 @@ class DefaultScriptHandlerTest extends Specification {
     }
 
     def "can configure dependencies"() {
-        def handler = handler()
-
         when:
         handler.dependencies {
             add('config', 'dep')
@@ -67,9 +104,4 @@ class DefaultScriptHandlerTest extends Specification {
         then:
         1 * dependencyHandler.add('config', 'dep')
     }
-
-    private DefaultScriptHandler handler() {
-        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/plugins/DefaultObjectConfigurationActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
index 626902f..1fbd866 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,10 +15,10 @@
  */
 package org.gradle.api.internal.plugins
 
-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.internal.initialization.ScriptHandlerInternal
 import org.gradle.configuration.ScriptPlugin
 import org.gradle.configuration.ScriptPluginFactory
 import org.gradle.groovy.scripts.DefaultScript
@@ -32,7 +32,7 @@ class DefaultObjectConfigurationActionTest extends Specification {
     def resolver = Mock(FileResolver)
     def scriptPluginFactory = Mock(ScriptPluginFactory)
     def scriptHandlerFactory = Mock(ScriptHandlerFactory)
-    def scriptHandler = Mock(ScriptHandler)
+    def scriptHandler = Mock(ScriptHandlerInternal)
     def scriptCompileScope = Mock(ClassLoaderScope)
     def parentCompileScope = Mock(ClassLoaderScope)
     def configurer = Mock(ScriptPlugin)
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 8a4a1b0..3028fba 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
@@ -40,7 +40,7 @@ public class ExtensionContainerTest extends Specification {
         expect:
         container.getByName(ExtraPropertiesExtension.EXTENSION_NAME) == container.extraProperties
     }
-    
+
     def "extension can be accessed and configured"() {
         when:
         container.add("foo", extension)
@@ -159,7 +159,7 @@ public class ExtensionContainerTest extends Specification {
         container.findByType(Parent) == child
         container.getByType(Parent) == child
     }
-    
+
     def "can create ExtensionAware extensions"() {
         given:
         container.add("foo", Parent)
@@ -167,10 +167,10 @@ public class ExtensionContainerTest extends Specification {
 
         expect:
         extension instanceof ExtensionAware
-        
+
         when:
         extension.extensions.create("thing", Thing, "bar")
-        
+
         then:
         extension.thing.name == "bar"
     }
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 26856eb..42bc36d 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
@@ -20,23 +20,12 @@ import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.ClassGenerator;
 import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.project.DefaultProject;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputDirectory;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.Nested;
-import org.gradle.api.tasks.OutputDirectories;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.OutputFiles;
-import org.gradle.api.tasks.SkipWhenEmpty;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.TaskValidationException;
+import org.gradle.api.tasks.*;
 import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
 import org.gradle.internal.UncheckedException;
 import org.gradle.test.fixtures.file.TestFile;
@@ -54,12 +43,7 @@ import spock.lang.Issue;
 
 import java.io.File;
 import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.Callable;
 
 import static org.gradle.util.Matchers.isEmpty;
@@ -175,7 +159,7 @@ public class AnnotationProcessingTaskFactoryTest {
     @Test
     public void failsWhenMultipleActionsAreIncremental() {
         assertTaskCreationFails(TaskWithMultipleIncrementalActions.class,
-                "Cannot have multiple @TaskAction methods accepting an IncrementalTaskInputs parameter.");
+            "Cannot have multiple @TaskAction methods accepting an IncrementalTaskInputs parameter.");
     }
 
     @Test
@@ -185,23 +169,23 @@ public class AnnotationProcessingTaskFactoryTest {
 
         assertThat(readField(task.getActions().get(0), Action.class, "action"), sameInstance(readField(task2.getActions().get(0), Action.class, "action")));
     }
-    
+
     @Test
     public void failsWhenStaticMethodHasTaskActionAnnotation() {
         assertTaskCreationFails(TaskWithStaticMethod.class,
-                "Cannot use @TaskAction annotation on static method TaskWithStaticMethod.doStuff().");
+            "Cannot use @TaskAction annotation on static method TaskWithStaticMethod.doStuff().");
     }
 
     @Test
     public void failsWhenMethodWithParametersHasTaskActionAnnotation() {
         assertTaskCreationFails(TaskWithMultiParamAction.class,
-                "Cannot use @TaskAction annotation on method TaskWithMultiParamAction.doStuff() as this method takes multiple parameters.");
+            "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.");
+            "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) {
@@ -223,7 +207,7 @@ public class AnnotationProcessingTaskFactoryTest {
         }});
         task.execute();
     }
-    
+
     @Test
     public void taskActionWorksForOverriddenMethods() {
         final Runnable action = context.mock(Runnable.class);
@@ -268,7 +252,7 @@ public class AnnotationProcessingTaskFactoryTest {
     public void validationActionFailsWhenInputFileIsADirectory() {
         TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingDir);
         assertValidationFails(task, String.format("File '%s' specified for property 'inputFile' is not a file.",
-                task.inputFile));
+            task.inputFile));
     }
 
     @Test
@@ -282,7 +266,7 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, new Object[]{null});
         assertThat(task.getInputs().getFiles().getFiles(), isEmpty());
     }
-    
+
     @Test
     public void validationActionSucceedsWhenSpecifiedOutputFileIsAFile() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingFile);
@@ -325,7 +309,7 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithOptionalOutputFiles task = expectTaskCreated(TaskWithOptionalOutputFiles.class);
         task.execute();
     }
-    
+
     @Test
     public void validationActionFailsWhenOutputFileNotSpecified() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new Object[]{null});
@@ -337,30 +321,30 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, new Object[]{null});
         assertValidationFails(task, "No value has been specified for property 'outputFiles'.");
     }
-    
+
     @Test
     public void validationActionFailsWhenSpecifiedOutputFileIsADirectory() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingDir);
         assertValidationFails(task, String.format(
-                "Cannot write to file '%s' specified for property 'outputFile' as it is a directory.",
-                task.outputFile));
+            "Cannot write to file '%s' specified for property 'outputFile' as it is a directory.",
+            task.outputFile));
     }
 
     @Test
     public void validationActionFailsWhenSpecifiedOutputFilesIsADirectory() {
         TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Collections.singletonList(existingDir));
         assertValidationFails(task, String.format(
-                "Cannot write to file '%s' specified for property 'outputFiles' as it is a directory.",
-                task.outputFiles.get(0)));
+            "Cannot write to file '%s' specified for property 'outputFiles' as it is a directory.",
+            task.outputFiles.get(0)));
     }
-    
+
     @Test
     public void validationActionFailsWhenSpecifiedOutputFileParentIsAFile() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new File(testDir, "subdir/output.txt"));
         GFileUtils.touch(task.outputFile.getParentFile());
 
         assertValidationFails(task, String.format("Cannot write to file '%s' specified for property 'outputFile', as ancestor '%s' is not a directory.",
-                task.getOutputFile(), task.outputFile.getParentFile()));
+            task.getOutputFile(), task.outputFile.getParentFile()));
     }
 
     @Test
@@ -369,9 +353,9 @@ public class AnnotationProcessingTaskFactoryTest {
         GFileUtils.touch(task.outputFiles.get(0).getParentFile());
 
         assertValidationFails(task, String.format("Cannot write to file '%s' specified for property 'outputFiles', as ancestor '%s' is not a directory.",
-                task.outputFiles.get(0), task.outputFiles.get(0).getParentFile()));
+            task.outputFiles.get(0), task.outputFiles.get(0).getParentFile()));
     }
-    
+
     @Test
     public void registersSpecifiedOutputFile() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingFile);
@@ -425,7 +409,7 @@ public class AnnotationProcessingTaskFactoryTest {
 
     @Test
     public void skipsTaskWhenInputFileCollectionIsEmpty() {
-        final FileCollection inputFiles = context.mock(FileCollection.class);
+        final FileCollectionInternal inputFiles = context.mock(FileCollectionInternal.class);
         context.checking(new Expectations() {{
             one(inputFiles).isEmpty();
             will(returnValue(true));
@@ -463,7 +447,7 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(existingDir));
         task.execute();
     }
-    
+
     @Test
     public void validationActionSucceedsWhenOptionalOutputDirectoryNotSpecified() {
         TaskWithOptionalOutputDir task = expectTaskCreated(TaskWithOptionalOutputDir.class);
@@ -492,16 +476,16 @@ public class AnnotationProcessingTaskFactoryTest {
     public void validationActionFailsWhenOutputDirectoryIsAFile() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, existingFile);
         assertValidationFails(task, String.format("Directory '%s' specified for property 'outputDir' is not a directory.",
-                task.outputDir));
+            task.outputDir));
     }
 
     @Test
     public void validationActionFailsWhenOutputDirectoriesIsAFile() {
         TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(existingFile));
         assertValidationFails(task, String.format("Directory '%s' specified for property 'outputDirs' is not a directory.",
-                task.outputDirs.get(0)));
+            task.outputDirs.get(0)));
     }
-    
+
     @Test
     public void validationActionFailsWhenParentOfOutputDirectoryIsAFile() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new File(testDir, "subdir/output"));
@@ -517,7 +501,7 @@ public class AnnotationProcessingTaskFactoryTest {
 
         assertValidationFails(task, String.format("Cannot write to directory '%s' specified for property 'outputDirs', as ancestor '%s' is not a directory.", task.outputDirs.get(0), task.outputDirs.get(0).getParentFile()));
     }
-    
+
     @Test
     public void registersSpecifiedOutputDirectory() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, missingDir);
@@ -529,7 +513,7 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Arrays.<File>asList(missingDir, missingDir2));
         assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(missingDir, missingDir2)));
     }
-    
+
     @Test
     public void doesNotRegisterOutputDirectoryWhenNoneSpecified() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new Object[]{null});
@@ -556,12 +540,12 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, new Object[]{null});
         assertValidationFails(task, "No value has been specified for property 'inputDir'.");
     }
-    
+
     @Test
     public void validationActionFailsWhenInputDirectoryDoesNotExist() {
         TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, missingDir);
         assertValidationFails(task, String.format("Directory '%s' specified for property 'inputDir' does not exist.",
-                task.inputDir));
+            task.inputDir));
     }
 
     @Test
@@ -570,7 +554,7 @@ public class AnnotationProcessingTaskFactoryTest {
         GFileUtils.touch(task.inputDir);
 
         assertValidationFails(task, String.format(
-                "Directory '%s' specified for property 'inputDir' is not a directory.", task.inputDir));
+            "Directory '%s' specified for property 'inputDir' is not a directory.", task.inputDir));
     }
 
     @Test
@@ -695,8 +679,8 @@ public class AnnotationProcessingTaskFactoryTest {
     public void validationFailureListsViolationsForAllProperties() {
         TaskWithMultipleProperties task = expectTaskCreated(TaskWithMultipleProperties.class, new Object[]{null});
         assertValidationFails(task,
-                "No value has been specified for property 'outputFile'.",
-                "No value has been specified for property 'bean.inputFile'.");
+            "No value has been specified for property 'outputFile'.",
+            "No value has been specified for property 'bean.inputFile'.");
     }
 
     @Test
@@ -788,7 +772,8 @@ public class AnnotationProcessingTaskFactoryTest {
             this.action = action;
         }
 
-        @Override @TaskAction
+        @Override
+        @TaskAction
         public void doStuff() {
             action.run();
         }
@@ -922,7 +907,9 @@ public class AnnotationProcessingTaskFactoryTest {
             super(inputDir);
         }
 
-        @Override @InputDirectory @SkipWhenEmpty
+        @Override
+        @InputDirectory
+        @SkipWhenEmpty
         public File getInputDir() {
             return super.getInputDir();
         }
@@ -959,9 +946,10 @@ public class AnnotationProcessingTaskFactoryTest {
             return outputFiles;
         }
     }
-    
+
     public static class TaskWithOptionalOutputFile extends DefaultTask {
-        @OutputFile @org.gradle.api.tasks.Optional
+        @OutputFile
+        @org.gradle.api.tasks.Optional
         public File getOutputFile() {
             return null;
         }
@@ -1000,16 +988,18 @@ public class AnnotationProcessingTaskFactoryTest {
             return outputDirs;
         }
     }
-    
+
     public static class TaskWithOptionalOutputDir extends DefaultTask {
-        @OutputDirectory @org.gradle.api.tasks.Optional
+        @OutputDirectory
+        @org.gradle.api.tasks.Optional
         public File getOutputDir() {
             return null;
         }
     }
 
     public static class TaskWithOptionalOutputDirs extends DefaultTask {
-        @OutputDirectories @org.gradle.api.tasks.Optional
+        @OutputDirectories
+        @org.gradle.api.tasks.Optional
         public File getOutputDirs() {
             return null;
         }
@@ -1033,7 +1023,8 @@ public class AnnotationProcessingTaskFactoryTest {
             super(input);
         }
 
-        @InputFiles @SkipWhenEmpty
+        @InputFiles
+        @SkipWhenEmpty
         public Iterable<? extends File> getInput() {
             return input;
         }
@@ -1045,7 +1036,8 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     public static class TaskWithOptionalInputFile extends DefaultTask {
-        @InputFile @org.gradle.api.tasks.Optional
+        @InputFile
+        @org.gradle.api.tasks.Optional
         public File getInputFile() {
             return null;
         }
@@ -1064,7 +1056,7 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
-    
+
     public static class TaskWithNestedBeanWithPrivateClass extends DefaultTask {
         Bean2 bean = new Bean2();
 
@@ -1078,7 +1070,7 @@ public class AnnotationProcessingTaskFactoryTest {
             return bean;
         }
     }
-    
+
     public static class TaskWithMultipleProperties extends TaskWithNestedBean {
         public TaskWithMultipleProperties(File inputFile) {
             super(inputFile);
@@ -1091,7 +1083,8 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     public static class TaskWithOptionalNestedBean extends DefaultTask {
-        @Nested @org.gradle.api.tasks.Optional
+        @Nested
+        @org.gradle.api.tasks.Optional
         public Bean getBean() {
             return null;
         }
@@ -1100,7 +1093,8 @@ public class AnnotationProcessingTaskFactoryTest {
     public static class TaskWithOptionalNestedBeanWithPrivateType extends DefaultTask {
         Bean2 bean = new Bean2();
 
-        @Nested @org.gradle.api.tasks.Optional
+        @Nested
+        @org.gradle.api.tasks.Optional
         public Bean getBean() {
             return null;
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainerTest.groovy
new file mode 100644
index 0000000..0e6c0d7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/AddOnlyRuleAwarePolymorphicDomainObjectContainerTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules
+
+import org.gradle.api.Named
+import org.gradle.internal.reflect.DirectInstantiator
+import spock.lang.Specification
+import spock.lang.Subject
+
+class AddOnlyRuleAwarePolymorphicDomainObjectContainerTest extends Specification {
+
+    class ElementType implements Named {
+        String name
+    }
+
+    @Subject
+    def container = new AddOnlyRuleAwarePolymorphicDomainObjectContainer(ElementType, DirectInstantiator.INSTANCE) {}
+
+    def "clear is not supported"() {
+        when:
+        container.clear()
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "remove is not supported"() {
+        when:
+        container.remove("foo")
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "removeAll is not supported"() {
+        when:
+        container.removeAll([])
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "retainAll is not supported"() {
+        when:
+        container.retainAll([])
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "iterator does not support removal of elements"() {
+        given:
+        def iterator = container.iterator()
+
+        when:
+        iterator.remove()
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistryTest.groovy
new file mode 100644
index 0000000..5bf1ee0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwareNamedDomainObjectFactoryRegistryTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules
+
+import org.gradle.api.GradleException
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import spock.lang.Specification
+
+class DefaultRuleAwareNamedDomainObjectFactoryRegistryTest extends Specification {
+
+    def delegate = Mock(NamedDomainObjectFactoryRegistry)
+    def registry = new DefaultRuleAwareNamedDomainObjectFactoryRegistry(delegate)
+
+    def "uses delegate to register factories"() {
+        given:
+        def factory = Mock(NamedDomainObjectFactory)
+
+        when:
+        registry.registerFactory(String, factory, null)
+
+        then:
+        1 * delegate.registerFactory(String, factory)
+    }
+
+    def "throws error when a factory for the same type is registered more than once"() {
+        given:
+        registry.registerFactory(String, {}, new SimpleModelRuleDescriptor("test rule"))
+
+        when:
+        registry.registerFactory(String, {}, null)
+
+        then:
+        GradleException e = thrown()
+        e.message == "Cannot register a factory for type String because a factory for this type was already registered by test rule."
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiatorTest.groovy
new file mode 100644
index 0000000..d4f6bda
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/DefaultRuleAwarePolymorphicNamedEntityInstantiatorTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.rules
+
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.internal.PolymorphicNamedEntityInstantiator
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import spock.lang.Specification
+
+class DefaultRuleAwarePolymorphicNamedEntityInstantiatorTest extends Specification {
+
+    def delegate = Mock(PolymorphicNamedEntityInstantiator)
+    def registry = Mock(RuleAwareNamedDomainObjectFactoryRegistry)
+    def instantiator = new DefaultRuleAwarePolymorphicNamedEntityInstantiator(delegate, registry)
+
+    def "uses delegate instantiator to create objects"() {
+        given:
+        delegate.create("foo", String) >> "bar"
+
+        expect:
+        instantiator.create("foo", String) == "bar"
+    }
+
+    def "uses delegate registry to register factories"() {
+        given:
+        def descriptor = new SimpleModelRuleDescriptor("test")
+        def factory = Mock(NamedDomainObjectFactory)
+
+        when:
+        instantiator.registerFactory(String, factory, descriptor)
+
+        then:
+        1 * registry.registerFactory(String, factory, descriptor)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy
new file mode 100644
index 0000000..4b71b3b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy
@@ -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.api.internal.rules
+
+import org.gradle.api.GradleException
+import org.gradle.api.Named
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import spock.lang.Specification
+
+class RuleAwarePolymorphicDomainObjectContainerTest extends Specification {
+
+    def container = new DummyContainer()
+
+    def "reports duplicate type registration that was created without rule context"() {
+        given:
+        container.registerFactory(Dummy, { new Dummy("foo") })
+
+        when:
+        container.registerFactory(Dummy, { new Dummy("other") })
+
+        then:
+        def t = thrown GradleException
+        t.message == "Cannot register a factory for type Dummy because a factory for this type is already registered."
+    }
+
+    def "reports duplicate type registration that was created with rule context"() {
+        given:
+        container.registerFactory(Dummy, { new Dummy(it) }, new SimpleModelRuleDescriptor("<model-rule>"))
+
+        when:
+        container.registerFactory(Dummy, { new Dummy(it) }, new SimpleModelRuleDescriptor("<other-rule>"))
+
+        then:
+        def t = thrown GradleException
+        t.message == "Cannot register a factory for type Dummy because a factory for this type was already registered by <model-rule>."
+    }
+
+    class DummyContainer extends RuleAwarePolymorphicDomainObjectContainer<Object> {
+        DummyContainer() {
+            super(Dummy.class, DirectInstantiator.INSTANCE)
+        }
+    }
+
+    class Dummy implements Named {
+        String name
+
+        Dummy(String name) {
+            this.name = name
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
index e370274..46ff9e6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
@@ -16,9 +16,9 @@
 package org.gradle.api.internal.tasks
 
 import org.gradle.api.file.FileCollection
-import org.gradle.api.file.FileTree
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.FileTreeInternal
 import org.gradle.util.UsesNativeServices
 import spock.lang.Specification
 
@@ -27,7 +27,7 @@ import java.util.concurrent.Callable
 @UsesNativeServices
 class DefaultTaskInputsTest extends Specification {
     private final File treeFile = new File('tree')
-    private final FileTree tree = [getFiles: { [treeFile] as Set}] as FileTree
+    private final tree = [getFiles: { [treeFile] as Set}] as FileTreeInternal
     private final FileResolver resolver = [
             resolve: {new File(it)},
             resolveFilesAsTree: {tree}
@@ -199,4 +199,4 @@ class DefaultTaskInputsTest extends Specification {
         inputs.hasInputs
         inputs.hasSourceFiles
     }
-}
\ No newline at end of file
+}
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 8a638b8..aad24a1 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
@@ -15,8 +15,9 @@
  */
 package org.gradle.api.internal.tasks.execution
 
-import org.gradle.api.file.FileCollection
+import org.gradle.api.execution.internal.TaskInputsListener
 import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.file.FileCollectionInternal
 import org.gradle.api.internal.tasks.TaskExecuter
 import org.gradle.api.internal.tasks.TaskExecutionContext
 import org.gradle.api.internal.tasks.TaskStateInternal
@@ -29,8 +30,9 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
     final TaskStateInternal state = Mock()
     final TaskExecutionContext executionContext = Mock()
     final TaskInputs taskInputs = Mock()
-    final FileCollection sourceFiles = Mock()
-    final SkipEmptySourceFilesTaskExecuter executer = new SkipEmptySourceFilesTaskExecuter(target)
+    final FileCollectionInternal sourceFiles = Mock()
+    def taskInputsListener = Mock(TaskInputsListener)
+    final SkipEmptySourceFilesTaskExecuter executer = new SkipEmptySourceFilesTaskExecuter(taskInputsListener, target)
 
     def setup() {
         _ * task.inputs >> taskInputs
@@ -49,6 +51,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         1 * state.upToDate()
         0 * target._
         0 * state._
+        1 * taskInputsListener.onExecute(task, sourceFiles)
     }
 
     def executesTaskWhenItsSourceFilesCollectionIsNotEmpty() {
@@ -63,6 +66,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         1 * target.execute(task, state, executionContext)
         0 * target._
         0 * state._
+        1 * taskInputsListener.onExecute(task, _)
     }
 
     def executesTaskWhenTaskHasNotDeclaredAnySourceFiles() {
@@ -76,5 +80,6 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification {
         1 * target.execute(task, state, executionContext)
         0 * target._
         0 * state._
+        1 * taskInputsListener.onExecute(task, _)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
index 2619749..7e58892 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
@@ -50,7 +50,22 @@ public class GradleBuildTest extends Specification {
         then:
         1 * launcherFactory.newInstance(task.startParameter) >> launcher
         1 * launcher.run() >> resultMock
-        1 * resultMock.rethrowFailure()
+        1 * launcher.stop()
+        0 * _._
+    }
+
+    void cleansUpOnBuildFailure() {
+        GradleLauncher launcher = Mock()
+        def failure = new RuntimeException()
+
+        when:
+        task.build()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * launcherFactory.newInstance(task.startParameter) >> launcher
+        1 * launcher.run() >> { throw failure }
         1 * launcher.stop()
         0 * _._
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
index 11d90de..2bf40e3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 package org.gradle.api.tasks.util
 
 import org.apache.tools.ant.DirectoryScanner
@@ -31,18 +31,21 @@ import static org.junit.Assert.*
 class PatternSetTest extends AbstractTestForPatternSet {
     PatternSet patternSet = new PatternSet()
 
-    @After void resetDefaultExcludes() {
+    @After
+    void resetDefaultExcludes() {
         DirectoryScanner.resetDefaultExcludes()
     }
 
-    @Test void testConstructionFromMap() {
+    @Test
+    void testConstructionFromMap() {
         Map map = [includes: [TEST_PATTERN_1], excludes: [TEST_PATTERN_2]]
         PatternFilterable patternSet = new PatternSet(map)
         assertThat(patternSet.includes, equalTo([TEST_PATTERN_1] as Set))
         assertThat(patternSet.excludes, equalTo([TEST_PATTERN_2] as Set))
     }
 
-    @Test void patternSetsAreEqualWhenAllPropertiesAreEqual() {
+    @Test
+    void patternSetsAreEqualWhenAllPropertiesAreEqual() {
         assertThat(new PatternSet(), strictlyEqual(new PatternSet()))
         assertThat(new PatternSet(caseSensitive: false), strictlyEqual(new PatternSet(caseSensitive: false)))
         assertThat(new PatternSet(includes: ['i']), strictlyEqual(new PatternSet(includes: ['i'])))
@@ -56,12 +59,13 @@ class PatternSetTest extends AbstractTestForPatternSet {
         assertThat(new PatternSet(excludes: ['e']), not(equalTo(new PatternSet(excludes: ['other']))))
     }
 
-    @Test void canCopyFromAnotherPatternSet() {
+    @Test
+    void canCopyFromAnotherPatternSet() {
         PatternSet other = new PatternSet()
         other.include 'a', 'b'
         other.exclude 'c'
-        other.include({true} as Spec)
-        other.exclude({false} as Spec)
+        other.include({ true } as Spec)
+        other.exclude({ false } as Spec)
         patternSet.copyFrom(other)
         assertThat(patternSet.includes, equalTo(['a', 'b'] as Set))
         assertThat(patternSet.excludes, equalTo(['c'] as Set))
@@ -71,12 +75,14 @@ class PatternSetTest extends AbstractTestForPatternSet {
         assertThat(patternSet.excludeSpecs, equalTo(other.excludeSpecs))
     }
 
-    @Test void createsSpecForEmptyPatternSet() {
+    @Test
+    void createsSpecForEmptyPatternSet() {
         included file('a')
         included file('b')
     }
 
-    @Test void usesDefaultGlobalExcludes() {
+    @Test
+    void usesDefaultGlobalExcludes() {
         excluded dir('.svn')
         excluded file('.svn', 'abc')
         excluded dir('a', 'b', '.svn')
@@ -84,7 +90,8 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('foo', '.DS_Store')
     }
 
-    @Test void takesGlobalExcludesFromAnt() {
+    @Test
+    void takesGlobalExcludesFromAnt() {
         DirectoryScanner.defaultExcludes.each {
             DirectoryScanner.removeDefaultExclude(it)
         }
@@ -97,7 +104,8 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('X')
     }
 
-    @Test void createsSpecForIncludePatterns() {
+    @Test
+    void createsSpecForIncludePatterns() {
         patternSet.include '*a*'
         patternSet.include '*b*'
 
@@ -106,16 +114,18 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('c')
     }
 
-    @Test void createsSpecForExcludePatterns() {
+    @Test
+    void createsSpecForExcludePatterns() {
         patternSet.exclude '*b*'
         patternSet.exclude '*c*'
-        
+
         included file('a')
         excluded file('b')
         excluded file('c')
     }
 
-    @Test void createsSpecForIncludeAndExcludePatterns() {
+    @Test
+    void createsSpecForIncludeAndExcludePatterns() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
 
@@ -126,21 +136,24 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('b')
     }
 
-    @Test void createsSpecForIncludeSpecs() {
+    @Test
+    void createsSpecForIncludeSpecs() {
         patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
 
         included file('a')
         excluded file('b')
     }
 
-    @Test void createsSpecForExcludeSpecs() {
+    @Test
+    void createsSpecForExcludeSpecs() {
         patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
 
         included file('a')
         excluded file('b')
     }
 
-    @Test void createsSpecForIncludeAndExcludeSpecs() {
+    @Test
+    void createsSpecForIncludeAndExcludeSpecs() {
         patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
         patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
 
@@ -150,21 +163,24 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('c')
     }
 
-    @Test void createsSpecForIncludeClosure() {
+    @Test
+    void createsSpecForIncludeClosure() {
         patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
 
         included file('a')
         excluded file('b')
     }
 
-    @Test void createsSpecForExcludeClosure() {
+    @Test
+    void createsSpecForExcludeClosure() {
         patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
 
         included file('a')
         excluded file('b')
     }
 
-    @Test void createsSpecForIncludeAndExcludeClosures() {
+    @Test
+    void createsSpecForIncludeAndExcludeClosures() {
         patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
         patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
 
@@ -173,17 +189,19 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('c')
     }
 
-    @Test void isCaseSensitiveByDefault() {
+    @Test
+    void isCaseSensitiveByDefault() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
-        
+
         included file('a')
         excluded file('A')
         excluded file('Ab')
         included file('aB')
     }
 
-    @Test void createsSpecForCaseInsensitivePatternSet() {
+    @Test
+    void createsSpecForCaseInsensitivePatternSet() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
         patternSet.caseSensitive = false
@@ -194,7 +212,8 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('bA')
     }
 
-    @Test void createIntersectPatternSet() {
+    @Test
+    void createIntersectPatternSet() {
         PatternSet basePatternSet = new PatternSet()
         basePatternSet.include '*a*'
         basePatternSet.include { FileTreeElement element -> element.file.name.contains('1') }
@@ -217,10 +236,23 @@ class PatternSetTest extends AbstractTestForPatternSet {
         excluded file('acd')
         excluded file('132')
         excluded file('132')
+
+        patternSet = new PatternSet().copyFrom(patternSet)
+        included file('ac')
+        included file('13')
+        excluded file('a')
+        excluded file('1')
+        excluded file('c')
+        excluded file('3')
+        excluded file('acb')
+        excluded file('acd')
+        excluded file('132')
+        excluded file('132')
     }
 
     @Issue("GRADLE-2566")
-    @Test void canUseGStringsAsIncludes() {
+    @Test
+    void canUseGStringsAsIncludes() {
         def a = "a*"
         def b = "b*"
 
@@ -233,7 +265,8 @@ class PatternSetTest extends AbstractTestForPatternSet {
     }
 
     @Issue("GRADLE-2566")
-    @Test void canUseGStringsAsExcludes() {
+    @Test
+    void canUseGStringsAsExcludes() {
         def a = "a"
         def b = "b"
 
@@ -255,8 +288,8 @@ class PatternSetTest extends AbstractTestForPatternSet {
 
     private FileTreeElement element(boolean isFile, String... elements) {
         [
-                getRelativePath: { return new RelativePath(isFile, elements) },
-                getFile: { return new File(elements.join('/')) }
+            getRelativePath: { return new RelativePath(isFile, elements) },
+            getFile        : { return new File(elements.join('/')) }
         ] as FileTreeElement
     }
 
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
index 364042a..1c51557 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/AbstractFileLockManagerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/AbstractFileLockManagerTest.groovy
@@ -25,6 +25,7 @@ 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.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
@@ -33,6 +34,7 @@ import spock.lang.Specification
 import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
 import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
 
+ at LeaksFileHandles
 abstract class AbstractFileLockManagerTest extends Specification {
     @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     def metaDataProvider = Mock(ProcessMetaDataProvider)
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 983e509..16cd8f3 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
@@ -49,10 +49,6 @@ class DefaultCacheFactoryTest extends Specification {
         _ * metaDataProvider.processDisplayName >> 'process'
     }
 
-    def cleanup() {
-        factory.close()
-    }
-
     public void "creates directory backed store instance"() {
         when:
         def cache = factory.openStore(tmpDir.testDirectory, "<display>", mode(Shared), null)
@@ -61,6 +57,9 @@ class DefaultCacheFactoryTest extends Specification {
         cache.reference.cache instanceof DefaultPersistentDirectoryStore
         cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
+
+        cleanup:
+        factory.close()
     }
 
     public void "creates directory backed cache instance"() {
@@ -71,6 +70,9 @@ class DefaultCacheFactoryTest extends Specification {
         cache.reference.cache instanceof DefaultPersistentDirectoryCache
         cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
+
+        cleanup:
+        factory.close()
     }
 
     public void "reuses directory backed cache instances"() {
@@ -84,6 +86,9 @@ class DefaultCacheFactoryTest extends Specification {
         and:
         1 * opened.execute(_)
         0 * opened._
+
+        cleanup:
+        factory.close()
     }
 
     public void "reuses directory backed store instances"() {
@@ -97,6 +102,9 @@ class DefaultCacheFactoryTest extends Specification {
         and:
         1 * opened.execute(_)
         0 * opened._
+
+        cleanup:
+        factory.close()
     }
 
     public void "closes cache instance when factory is closed"() {
@@ -190,6 +198,9 @@ class DefaultCacheFactoryTest extends Specification {
         then:
         IllegalStateException e = thrown()
         e.message == "Cache '${tmpDir.testDirectory}' is already open with different state."
+
+        cleanup:
+        factory.close()
     }
 
     public void "fails when directory cache when cache is already open with different lock mode"() {
@@ -202,6 +213,9 @@ class DefaultCacheFactoryTest extends Specification {
         then:
         IllegalStateException e = thrown()
         e.message == "Cache '${tmpDir.testDirectory}' is already open with different options."
+
+        cleanup:
+        factory.close()
     }
 
     public void "can pass CacheValidator to Cache"() {
@@ -214,5 +228,8 @@ class DefaultCacheFactoryTest extends Specification {
         then:
         validator.isValid() >>> [false, true]
         cache != null
+
+        cleanup:
+        factory.close()
     }
 }
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 ea79581..db752ce 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
@@ -29,7 +29,7 @@ import static org.gradle.cache.internal.filelock.LockOptionsBuilder.mode
 class DefaultPersistentDirectoryCacheSpec extends Specification {
 
     @Rule TestNameTestDirectoryProvider tmp
-    
+
     def "will rebuild cache if not unlocked cleanly"() {
         given:
         def dir = tmp.createDir("cache")
@@ -39,11 +39,14 @@ class DefaultPersistentDirectoryCacheSpec extends Specification {
         def cache = new DefaultPersistentDirectoryCache(
                 dir, "test", { true } as CacheValidator, [:], mode(FileLockManager.LockMode.Exclusive), init, createDefaultFileLockManager()
         )
-        
+
         when:
         cache.open()
 
         then:
         initd
+
+        cleanup:
+        cache.close()
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreConcurrencyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreConcurrencyTest.groovy
index 8009dba..900e538 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreConcurrencyTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreConcurrencyTest.groovy
@@ -54,5 +54,8 @@ class DefaultPersistentDirectoryStoreConcurrencyTest extends ConcurrentSpec {
 
         then:
         noExceptionThrown()
+
+        cleanup:
+        store.close()
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
index 69e4ca7..b6c92f3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
@@ -43,45 +43,60 @@ public class BTreePersistentIndexedCacheTest {
     @Before
     public void setup() {
         cacheFile = tmpDir.file("cache.bin");
+    }
+
+    private void createCache() {
         cache = new BTreePersistentIndexedCache<String, Integer>(cacheFile, stringSerializer, integerSerializer, (short) 4, 100);
     }
 
+    private void verifyAndCloseCache() {
+        cache.verify();
+        cache.close();
+    }
+
     @Test
     public void getReturnsNullWhenEntryDoesNotExist() {
+        createCache();
         assertNull(cache.get("unknown"));
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsAddedEntries() {
+        createCache();
         checkAdds(1, 2, 3, 4, 5);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsAddedEntriesInReverseOrder() {
+        createCache();
         checkAdds(5, 4, 3, 2, 1);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsAddedEntriesOverMultipleIndexBlocks() {
+        createCache();
         checkAdds(3, 2, 11, 5, 7, 1, 10, 8, 9, 4, 6, 0);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsAddedEntriesAfterReopen() {
+        createCache();
+
         checkAdds(1, 2, 3, 4);
 
         cache.reset();
 
         checkAdds(5, 6, 7, 8);
-        cache.verify();
+        verifyAndCloseCache();
     }
-    
+
     @Test
     public void persistsReplacedEntries() {
+        createCache();
 
         cache.put("key_1", 1);
         cache.put("key_2", 2);
@@ -106,7 +121,7 @@ public class BTreePersistentIndexedCacheTest {
         assertThat(cache.get("key_4"), equalTo(12));
         assertThat(cache.get("key_5"), equalTo(5));
 
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
@@ -135,11 +150,13 @@ public class BTreePersistentIndexedCacheTest {
 
         cache.put("key_1", "1234");
         assertThat(cacheFile.length(), equalTo(len));
+
+        cache.close();
     }
-    
+
     @Test
     public void canHandleLargeNumberOfEntries() {
-
+        createCache();
         int count = 2000;
         List<Integer> values = new ArrayList<Integer>();
         for (int i = 0; i < count; i++) {
@@ -156,68 +173,77 @@ public class BTreePersistentIndexedCacheTest {
         assertThat(cacheFile.length(), lessThan((long)(1.4 * len)));
 
         checkAdds(values);
-        
+
         // need to make this better
         assertThat(cacheFile.length(), lessThan((long) (1.4 * 1.4 * len)));
+
+        cache.close();
     }
 
     @Test
     public void persistsRemovalOfEntries() {
+        createCache();
         checkAddsAndRemoves(1, 2, 3, 4, 5);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsRemovalOfEntriesInReverse() {
+        createCache();
         checkAddsAndRemoves(Collections.<Integer>reverseOrder(), 1, 2, 3, 4, 5);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void persistsRemovalOfEntriesOverMultipleIndexBlocks() {
+        createCache();
         checkAddsAndRemoves(4, 12, 9, 1, 3, 10, 11, 7, 8, 2, 5, 6);
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void removalRedistributesRemainingEntriesWithLeftSibling() {
+        createCache();
         // Ends up with: 1 2 3 -> 4 <- 5 6
         checkAdds(1, 2, 5, 6, 4, 3);
         cache.verify();
         cache.remove("key_5");
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void removalMergesRemainingEntriesIntoLeftSibling() {
+        createCache();
         // Ends up with: 1 2 -> 3 <- 4 5
         checkAdds(1, 2, 4, 5, 3);
         cache.verify();
         cache.remove("key_4");
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void removalRedistributesRemainingEntriesWithRightSibling() {
+        createCache();
         // Ends up with: 1 2 -> 3 <- 4 5 6
         checkAdds(1, 2, 4, 5, 3, 6);
         cache.verify();
         cache.remove("key_2");
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void removalMergesRemainingEntriesIntoRightSibling() {
+        createCache();
         // Ends up with: 1 2 -> 3 <- 4 5
         checkAdds(1, 2, 4, 5, 3);
         cache.verify();
         cache.remove("key_2");
-        cache.verify();
+        verifyAndCloseCache();
     }
 
     @Test
     public void handlesBadlyFormedCacheFile() throws IOException {
-        cacheFile.assertIsFile();
+        cacheFile.createNewFile();
         cacheFile.write("some junk");
 
         BTreePersistentIndexedCache<String, Integer> cache = new BTreePersistentIndexedCache<String, Integer>(cacheFile, stringSerializer, integerSerializer);
@@ -227,11 +253,14 @@ public class BTreePersistentIndexedCacheTest {
 
         RandomAccessFile file = new RandomAccessFile(cacheFile, "rw");
         file.setLength(file.length() - 10);
+        file.close();
 
         cache.reset();
 
         assertNull(cache.get("key_1"));
         cache.verify();
+
+        cache.close();
     }
 
     @Test
@@ -245,10 +274,14 @@ public class BTreePersistentIndexedCacheTest {
         assertThat(cache.get(new File("file")), equalTo(1));
         assertThat(cache.get(new File("dir/file")), equalTo(2));
         assertThat(cache.get(new File("File")), equalTo(3));
+
+        cache.close();
     }
 
     @Test
     public void handlesKeysWithSameHashCode() {
+        createCache();
+
         String key1 = new String(new byte[]{2, 31});
         String key2 = new String(new byte[]{1, 62});
         cache.put(key1, 1);
@@ -256,6 +289,8 @@ public class BTreePersistentIndexedCacheTest {
 
         assertThat(cache.get(key1), equalTo(1));
         assertThat(cache.get(key2), equalTo(2));
+
+        cache.close();
     }
 
     private void checkAdds(Integer... values) {
@@ -313,4 +348,4 @@ public class BTreePersistentIndexedCacheTest {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy
index 322d9eb..693779b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.groovy
@@ -15,10 +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.api.internal.initialization.ScriptHandlerInternal
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.initialization.InitScript
 import org.gradle.internal.resource.Resource
@@ -39,7 +39,7 @@ class DefaultInitScriptProcessorTest extends Specification {
         }
         def gradleMock = Mock(GradleInternal)
         def siblingScope = Mock(ClassLoaderScope)
-        def scriptHandler = Mock(ScriptHandler)
+        def scriptHandler = Mock(ScriptHandlerInternal)
         def scriptPlugin = Mock(ScriptPlugin)
 
         1 * gradleMock.getClassLoaderScope() >> gradleScope
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy
index aaf84e5..fd168d5 100755
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.api.internal.DocumentationRegistry
 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.groovy.scripts.*
 import org.gradle.groovy.scripts.internal.CompiledScript
 import org.gradle.groovy.scripts.internal.FactoryBackedCompileOperation
@@ -48,7 +49,7 @@ public class DefaultScriptPluginFactoryTest extends Specification {
     def baseChildClassLoader = Mock(ClassLoader)
     def scriptHandlerFactory = Mock(ScriptHandlerFactory)
     def pluginRequestApplicator = Mock(PluginRequestApplicator)
-    def scriptHandler = Mock(ScriptHandler)
+    def scriptHandler = Mock(ScriptHandlerInternal)
     def classPathScriptRunner = Mock(ScriptRunner)
     def classPathScript = Mock(BasicScript)
     def loggingManagerFactory = Mock(Factory) as Factory<LoggingManagerInternal>
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultCancellableOperationManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultCancellableOperationManagerTest.groovy
new file mode 100644
index 0000000..48c5247
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultCancellableOperationManagerTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.initialization.DefaultBuildCancellationToken
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import org.gradle.util.DisconnectableInputStream
+import spock.lang.AutoCleanup
+
+import java.util.concurrent.Executors
+
+class DefaultCancellableOperationManagerTest extends ConcurrentSpec {
+
+    @AutoCleanup
+    def writeEnd = new PipedOutputStream()
+    @AutoCleanup("shutdownNow")
+    def executorService = Executors.newCachedThreadPool()
+    def cancellationToken = new DefaultBuildCancellationToken()
+    def monitor = new DefaultCancellableOperationManager(executorService, new DisconnectableInputStream(new PipedInputStream(writeEnd)), cancellationToken)
+
+    def "can exit without cancel"() {
+        when:
+        monitor.monitorInput {}
+
+        then:
+        !cancellationToken.isCancellationRequested()
+        executorService.shutdownNow().empty
+    }
+
+
+    def "closing input after monitoring doesn't trigger cancel"() {
+        when:
+        start {
+            monitor.monitorInput {}
+            writeEnd.close()
+            instant.done
+        }
+
+        thread.blockUntil.done
+
+        then:
+        !cancellationToken.isCancellationRequested()
+        executorService.shutdownNow().empty
+    }
+
+    def "can throw"() {
+        when:
+        monitor.monitorInput {
+            throw new Exception("!")
+        }
+
+        then:
+        thrown Exception
+        !cancellationToken.isCancellationRequested()
+        executorService.shutdownNow().empty
+    }
+
+    def "triggers cancel when input is closed"() {
+        when:
+        start {
+            monitor.monitorInput {
+                instant.started
+                it.addCallback {
+                    instant.cancelled
+                }
+                thread.blockUntil.cancelled
+            }
+            instant.finished
+        }
+
+        thread.blockUntil.started
+        writeEnd.close()
+        thread.blockUntil.finished
+
+        then:
+        cancellationToken.isCancellationRequested()
+        executorService.shutdownNow().empty
+    }
+
+    def "triggers cancel when input contains EOT"() {
+        when:
+        start {
+            monitor.monitorInput {
+                instant.started
+                it.addCallback {
+                    instant.cancelled
+                }
+                thread.blockUntil.cancelled
+            }
+            instant.finished
+        }
+
+        thread.blockUntil.started
+        writeEnd.write(4)
+        thread.blockUntil.finished
+
+        then:
+        cancellationToken.isCancellationRequested()
+        executorService.shutdownNow().empty
+    }
+
+}
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 6a9ddd3..15baaff 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolverTest.groovy
@@ -20,11 +20,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.TaskContainerInternal
-import org.gradle.model.collection.CollectionBuilder
-import org.gradle.model.internal.core.DefaultCollectionBuilder
-import org.gradle.model.internal.core.ModelCreators
-import org.gradle.model.internal.core.ModelReference
-import org.gradle.model.internal.core.NamedEntityInstantiator
+import org.gradle.model.ModelMap
 import org.gradle.model.internal.fixture.ModelRegistryHelper
 import spock.lang.Specification
 
@@ -53,7 +49,7 @@ class TaskNameResolverTest extends Specification {
 
     def "eagerly locates task with given name for single project"() {
         when:
-        tasks(registry) { it.create("task")}
+        tasks(registry) { it.create("task") }
         def candidates = resolver.selectWithName('task', project, false)
 
         then:
@@ -263,11 +259,12 @@ class TaskNameResolverTest extends Specification {
         Stub(TaskInternal) { TaskInternal task ->
             _ * task.getName() >> name
             _ * task.getDescription() >> description
+            _ * task.configure(_) >> { task.with(it[0]); task }
         }
     }
 
-    def tasks(ModelRegistryHelper registry, Action<? super CollectionBuilder<TaskInternal>> action) {
-        registry.mutateCollection("tasks", TaskInternal, action)
+    def tasks(ModelRegistryHelper registry, Action<? super ModelMap<TaskInternal>> action) {
+        registry.mutateModelMap("tasks", TaskInternal, action)
     }
 
     Set<Task> asTasks(TaskSelectionResult taskSelectionResult) {
@@ -277,11 +274,10 @@ class TaskNameResolverTest extends Specification {
     }
 
     private ModelRegistryHelper createTasksCollection(ModelRegistryHelper registry, String description) {
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(TaskInternal)
-        def iRef = ModelReference.of("instantiator", iType)
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> task(name, description) } as NamedEntityInstantiator).build())
-                .collection("tasks", TaskInternal, iRef)
+        registry.modelMap("tasks", TaskInternal) {
+            it.registerFactory(TaskInternal) {
+                task(it, description)
+            }
+        }
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterSpec.groovy
index a9b2d89..61f577e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterSpec.groovy
@@ -18,23 +18,201 @@ package org.gradle.execution.taskgraph
 
 import org.gradle.api.BuildCancelledException
 import org.gradle.api.Task
+import org.gradle.api.execution.TaskExecutionListener
+import org.gradle.api.execution.internal.InternalTaskExecutionListener
+import org.gradle.api.execution.internal.TaskOperationInternal
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.internal.tasks.TaskExecuter
 import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.initialization.BuildCancellationToken
-import org.gradle.internal.event.ListenerBroadcast
-import org.gradle.internal.event.ListenerManager
+import org.gradle.internal.Factories
+import org.gradle.internal.TimeProvider
+import org.gradle.internal.event.DefaultListenerManager
+import org.gradle.internal.progress.BuildOperationExecutor
+import org.gradle.internal.progress.OperationResult
+import org.gradle.internal.progress.OperationStartEvent
 import org.gradle.testfixtures.ProjectBuilder
 import spock.lang.Specification
 
 class DefaultTaskGraphExecuterSpec extends Specification {
     def cancellationToken = Mock(BuildCancellationToken)
     def project = ProjectBuilder.builder().build()
-    def listenerManager = Stub(ListenerManager) {
-        _ * createAnonymousBroadcaster(_) >> { Class cl -> new ListenerBroadcast(cl) }
+    def listenerManager = new DefaultListenerManager()
+    def executer = Mock(TaskExecuter)
+    def taskExecuter = new DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor(), Factories.constant(executer), cancellationToken, Stub(TimeProvider), Stub(BuildOperationExecutor))
+
+    def "notifies task listener as tasks are executed"() {
+        def listener = Mock(TaskExecutionListener)
+        def a = task("a")
+        def b = task("b")
+
+        given:
+        taskExecuter.addTaskExecutionListener(listener)
+        taskExecuter.addTasks([a, b])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        1 * listener.beforeExecute(a)
+        1 * listener.afterExecute(a, a.state)
+
+        then:
+        1 * listener.beforeExecute(b)
+        1 * listener.afterExecute(b, b.state)
+        0 * listener._
+    }
+
+    def "notifies task listener when task fails"() {
+        def listener = Mock(TaskExecutionListener)
+        def failure = new RuntimeException()
+        def a = brokenTask("a", failure)
+
+        given:
+        taskExecuter.addTaskExecutionListener(listener)
+        taskExecuter.addTasks([a])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * listener.beforeExecute(a)
+        1 * listener.afterExecute(a, a.state)
+        0 * listener._
+    }
+
+    def "notifies internal task listener as tasks are executed"() {
+        def listener = Mock(InternalTaskExecutionListener)
+        def a = task("a")
+        def failure = new RuntimeException()
+        def b = brokenTask("b", failure)
+
+        given:
+        listenerManager.addListener(listener)
+        taskExecuter.addTasks([a, b])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * listener.beforeExecute(_, _) >> { TaskOperationInternal operation, OperationStartEvent startEvent ->
+            assert operation.task == a
+        }
+        1 * listener.afterExecute(_, _) >> { TaskOperationInternal operation, OperationResult result ->
+            assert operation.task == a
+            assert result.failure == null
+        }
+
+        then:
+        1 * listener.beforeExecute(_, _) >> { TaskOperationInternal operation, OperationStartEvent startEvent ->
+            assert operation.task == b
+        }
+        1 * listener.afterExecute(_, _) >> { TaskOperationInternal operation, OperationResult result ->
+            assert operation.task == b
+            assert result.failure == failure
+        }
+        0 * listener._
+    }
+
+    def "wraps notification of internal listener around public listener"() {
+        def listener1 = Mock(InternalTaskExecutionListener)
+        def listener2 = Mock(TaskExecutionListener)
+        def a = task("a")
+        def b = task("b")
+
+        given:
+        listenerManager.addListener(listener1)
+        listenerManager.addListener(listener2)
+        taskExecuter.addTasks([a, b])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        1 * listener1.beforeExecute({it.task == a}, _)
+
+        then:
+        1 * listener2.beforeExecute(a)
+        1 * listener2.afterExecute(a, a.state)
+
+        then:
+        1 * listener1.afterExecute({it.task == a}, _)
+
+        then:
+        1 * listener1.beforeExecute({it.task == b}, _)
+
+        then:
+        1 * listener2.beforeExecute(b)
+        1 * listener2.afterExecute(b, b.state)
+
+        then:
+        1 * listener1.afterExecute({it.task == b}, _)
+        0 * listener1._
+        0 * listener2._
+    }
+
+    def "notifies internal listener of completion when public listener fails on task start"() {
+        def listener1 = Mock(InternalTaskExecutionListener)
+        def listener2 = Mock(TaskExecutionListener)
+        def failure = new RuntimeException()
+        def a = task("a")
+
+        given:
+        listenerManager.addListener(listener1)
+        listenerManager.addListener(listener2)
+        taskExecuter.addTasks([a])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * listener1.beforeExecute({it.task == a}, _)
+
+        then:
+        1 * listener2.beforeExecute(a) >> { throw failure }
+
+        then:
+        1 * listener1.afterExecute({it.task == a}, _)
+        0 * listener1._
+        0 * listener2._
+    }
+
+    def "notifies internal listener of completion when public listener fails on task complete"() {
+        def listener1 = Mock(InternalTaskExecutionListener)
+        def listener2 = Mock(TaskExecutionListener)
+        def failure = new RuntimeException()
+        def a = task("a")
+
+        given:
+        listenerManager.addListener(listener1)
+        listenerManager.addListener(listener2)
+        taskExecuter.addTasks([a])
+
+        when:
+        taskExecuter.execute()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * listener1.beforeExecute({it.task == a}, _)
+
+        then:
+        1 * listener2.beforeExecute(a)
+        1 * listener2.afterExecute(a, a.state) >> { throw failure }
+
+        then:
+        1 * listener1.afterExecute({it.task == a}, _)
+        0 * listener1._
+        0 * listener2._
     }
-    def taskExecuter = new DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor(), cancellationToken)
 
     def "stops running tasks and fails with exception when build is cancelled"() {
         def a = task("a")
@@ -52,8 +230,8 @@ class DefaultTaskGraphExecuterSpec extends Specification {
         e.message == 'Build cancelled.'
 
         and:
-        1 * a.executeWithoutThrowingTaskFailure()
-        0 * b.executeWithoutThrowingTaskFailure()
+        1 * executer.execute(a, a.state, _)
+        0 * executer._
     }
 
     def "does not fail with exception when build is cancelled after last task has started"() {
@@ -68,8 +246,9 @@ class DefaultTaskGraphExecuterSpec extends Specification {
         taskExecuter.execute()
 
         then:
-        1 * a.executeWithoutThrowingTaskFailure()
-        1 * b.executeWithoutThrowingTaskFailure()
+        1 * executer.execute(a, a.state, _)
+        1 * executer.execute(b, b.state, _)
+        0 * executer._
     }
 
     def "does not fail with exception when build is cancelled and no tasks scheduled"() {
@@ -101,4 +280,22 @@ class DefaultTaskGraphExecuterSpec extends Specification {
         }
         return mock
     }
+
+    def brokenTask(String name, RuntimeException failure) {
+        def mock = Mock(TaskInternal)
+        _ * mock.name >> name
+        _ * mock.project >> project
+        _ * mock.state >> Stub(TaskStateInternal) {
+            getFailure() >> failure
+        }
+        _ * mock.taskDependencies >> Stub(TaskDependency)
+        _ * mock.finalizedBy >> Stub(TaskDependency)
+        _ * mock.mustRunAfter >> Stub(TaskDependency)
+        _ * mock.shouldRunAfter >> Stub(TaskDependency)
+        _ * mock.compareTo(_) >> { Task t -> name.compareTo(t.name) }
+        _ * mock.outputs >> Stub(TaskOutputsInternal) {
+            getFiles() >> project.files()
+        }
+        return mock
+    }
 }
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 6563446..f620cb9 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
@@ -21,19 +21,20 @@ import org.gradle.api.CircularReferenceException;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionGraphListener;
 import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.execution.internal.InternalTaskExecutionListener;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.DefaultTaskDependency;
-import org.gradle.api.internal.tasks.DefaultTaskOutputs;
-import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.internal.tasks.*;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.TaskOutputs;
-import org.gradle.api.tasks.TaskState;
 import org.gradle.execution.TaskFailureHandler;
 import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.internal.Factories;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.event.ListenerBroadcast;
 import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.progress.BuildOperationExecutor;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TestClosure;
 import org.hamcrest.Description;
@@ -60,6 +61,8 @@ public class DefaultTaskGraphExecuterTest {
     final JUnit4Mockery context = new JUnit4GroovyMockery();
     final ListenerManager listenerManager = context.mock(ListenerManager.class);
     final BuildCancellationToken cancellationToken = context.mock(BuildCancellationToken.class);
+    final BuildOperationExecutor buildOperationExecutor = context.mock(BuildOperationExecutor.class);
+    final TaskExecuter executer = context.mock(TaskExecuter.class);
     DefaultTaskGraphExecuter taskExecuter;
     ProjectInternal root;
     List<Task> executedTasks = new ArrayList<Task>();
@@ -72,9 +75,12 @@ public class DefaultTaskGraphExecuterTest {
             will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
             one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
             will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
+            one(listenerManager).createAnonymousBroadcaster(InternalTaskExecutionListener.class);
+            will(returnValue(new ListenerBroadcast<InternalTaskExecutionListener>(InternalTaskExecutionListener.class)));
             allowing(cancellationToken).isCancellationRequested();
+            allowing(buildOperationExecutor).getCurrentOperationId();
         }});
-        taskExecuter = new DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor(), cancellationToken);
+        taskExecuter = new DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor(), Factories.constant(executer), cancellationToken, new TrueTimeProvider(), buildOperationExecutor);
     }
 
     @Test
@@ -283,49 +289,6 @@ public class DefaultTaskGraphExecuterTest {
     }
 
     @Test
-    public void testNotifiesTaskListenerAsTasksAreExecuted() {
-        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
-        final Task a = task("a");
-        final Task b = task("b");
-
-        taskExecuter.addTaskExecutionListener(listener);
-        taskExecuter.addTasks(toList(a, b));
-
-        context.checking(new Expectations() {{
-            one(listener).beforeExecute(a);
-            one(listener).afterExecute(with(equalTo(a)), with(notNullValue(TaskState.class)));
-            one(listener).beforeExecute(b);
-            one(listener).afterExecute(with(equalTo(b)), with(notNullValue(TaskState.class)));
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    public void testNotifiesTaskListenerWhenTaskFails() {
-        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
-        final RuntimeException failure = new RuntimeException();
-        final Task a = brokenTask("a", failure);
-
-        taskExecuter.addTaskExecutionListener(listener);
-        taskExecuter.addTasks(toList(a));
-
-        context.checking(new Expectations() {{
-            one(listener).beforeExecute(a);
-            one(listener).afterExecute(with(sameInstance(a)), with(notNullValue(TaskState.class)));
-        }});
-
-        try {
-            taskExecuter.execute();
-            fail();
-        } catch (RuntimeException e) {
-            assertThat(e, sameInstance(failure));
-        }
-        
-        assertThat(executedTasks, equalTo(toList(a)));
-    }
-
-    @Test
     public void testStopsExecutionOnFirstFailureWhenNoFailureHandlerProvided() {
         final RuntimeException failure = new RuntimeException();
         final Task a = brokenTask("a", failure);
@@ -342,7 +305,7 @@ public class DefaultTaskGraphExecuterTest {
 
         assertThat(executedTasks, equalTo(toList(a)));
     }
-    
+
     @Test
     public void testStopsExecutionOnFailureWhenFailureHandlerIndicatesThatExecutionShouldStop() {
         final TaskFailureHandler handler = context.mock(TaskFailureHandler.class);
@@ -421,7 +384,7 @@ public class DefaultTaskGraphExecuterTest {
         assertThat(taskExecuter.getAllTasks(), equalTo(toList(b)));
 
         taskExecuter.execute();
-        
+
         assertThat(executedTasks, equalTo(toList(b)));
     }
 
@@ -439,9 +402,9 @@ public class DefaultTaskGraphExecuterTest {
         taskExecuter.useFilter(spec);
         taskExecuter.addTasks(toList(c));
         assertThat(taskExecuter.getAllTasks(), equalTo(toList(b, c)));
-        
+
         taskExecuter.execute();
-                
+
         assertThat(executedTasks, equalTo(toList(b, c)));
     }
 
@@ -483,7 +446,7 @@ public class DefaultTaskGraphExecuterTest {
             will(returnValue(toSet(dependsOn)));
         }});
     }
-    
+
     private Task brokenTask(String name, final RuntimeException failure, final Task... dependsOn) {
         final TaskInternal task = context.mock(TaskInternal.class);
         final TaskStateInternal state = context.mock(TaskStateInternal.class);
@@ -491,7 +454,7 @@ public class DefaultTaskGraphExecuterTest {
         setExpectations(name, task, state, outputs);
         dependsOn(task, dependsOn);
         context.checking(new Expectations() {{
-            atMost(1).of(task).executeWithoutThrowingTaskFailure();
+            atMost(1).of(executer).execute(with(sameInstance(task)), with(sameInstance(state)), with(notNullValue(TaskExecutionContext.class)));
             will(new ExecuteTaskAction(task));
             allowing(state).getFailure();
             will(returnValue(failure));
@@ -500,7 +463,7 @@ public class DefaultTaskGraphExecuterTest {
         }});
         return task;
     }
-    
+
     private Task task(final String name, final Task... dependsOn) {
         final TaskInternal task = context.mock(TaskInternal.class);
         final TaskStateInternal state = context.mock(TaskStateInternal.class);
@@ -508,14 +471,14 @@ public class DefaultTaskGraphExecuterTest {
         setExpectations(name, task, state, outputs);
         dependsOn(task, dependsOn);
         context.checking(new Expectations() {{
-            atMost(1).of(task).executeWithoutThrowingTaskFailure();
+            atMost(1).of(executer).execute(with(sameInstance(task)), with(sameInstance(state)), with(notNullValue(TaskExecutionContext.class)));
             will(new ExecuteTaskAction(task));
             allowing(state).getFailure();
             will(returnValue(null));
         }});
         return task;
     }
-    
+
     private TaskInternal createTask(final String name) {
         TaskInternal task = context.mock(TaskInternal.class);
         TaskStateInternal state = context.mock(TaskStateInternal.class);
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
index 5961136..e9e88d3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutorTest.groovy
@@ -16,27 +16,34 @@
 
 package org.gradle.execution.taskgraph
 
-import org.gradle.api.execution.TaskExecutionListener
+import org.gradle.api.Action
+import org.gradle.api.Project
 import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.tasks.TaskStateInternal
+import org.gradle.api.invocation.Gradle
 import spock.lang.Specification
 
 class DefaultTaskPlanExecutorTest extends Specification {
     def taskPlan = Mock(TaskExecutionPlan)
-    def executionListener = Mock(TaskExecutionListener)
+    def worker = Mock(Action)
     def executor = new DefaultTaskPlanExecutor()
 
     def "executes tasks until no further tasks remain"() {
+        def gradle = Mock(Gradle)
+        def project = Mock(Project)
         def task = Mock(TaskInternal)
+        def state = Mock(TaskStateInternal)
+        project.gradle >> gradle
+        task.project >> project
+        task.state >> state
         def taskInfo = new TaskInfo(task)
 
         when:
-        executor.process(taskPlan, executionListener)
+        executor.process(taskPlan, worker)
 
         then:
         1 * taskPlan.taskToExecute >> taskInfo
-        1 * executionListener.beforeExecute(task)
-        1 * task.executeWithoutThrowingTaskFailure()
-        1 * executionListener.afterExecute(task, _)
+        1 * worker.execute(task)
         1 * taskPlan.taskComplete(taskInfo)
         1 * taskPlan.taskToExecute >> null
         1 * taskPlan.awaitCompletion()
@@ -49,7 +56,7 @@ class DefaultTaskPlanExecutorTest extends Specification {
         _ * taskPlan.awaitCompletion() >> { throw failure }
 
         when:
-        executor.process(taskPlan, executionListener)
+        executor.process(taskPlan, worker)
 
         then:
         def e = thrown(RuntimeException)
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 83fbbc5..02eb3e9 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
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-package org.gradle.execution.taskgraph;
-
+package org.gradle.execution.taskgraph
 
 import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess
 import org.gradle.internal.concurrent.ExecutorFactory
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineConverterTestSupport.java b/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineConverterTestSupport.java
new file mode 100644
index 0000000..8fc0291
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineConverterTestSupport.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging.LogLevel;
+import org.gradle.logging.ConsoleOutput;
+import org.gradle.logging.ShowStacktrace;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.*;
+
+import static org.junit.Assert.assertEquals;
+
+public class CommandLineConverterTestSupport {
+    protected TestFile currentDir;
+    protected File expectedBuildFile;
+    protected File expectedGradleUserHome = new BuildLayoutParameters().getGradleUserHomeDir();
+    protected File expectedCurrentDir;
+    protected File expectedProjectDir;
+    protected List<String> expectedTaskNames = WrapUtil.toList();
+    protected Set<String> expectedExcludedTasks = WrapUtil.toSet();
+    protected boolean buildProjectDependencies = true;
+    protected Map<String, String> expectedSystemProperties = new HashMap<String, String>();
+    protected Map<String, String> expectedProjectProperties = new HashMap<String, String>();
+    protected List<File> expectedInitScripts = new ArrayList<File>();
+    protected boolean expectedSearchUpwards = true;
+    protected boolean expectedDryRun;
+    protected ShowStacktrace expectedShowStackTrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
+    protected LogLevel expectedLogLevel = LogLevel.LIFECYCLE;
+    protected boolean expectedColorOutput = true;
+    protected ConsoleOutput expectedConsoleOutput = ConsoleOutput.Auto;
+    protected StartParameter actualStartParameter;
+    protected boolean expectedProfile;
+    protected File expectedProjectCacheDir;
+    protected boolean expectedRefreshDependencies;
+    protected boolean expectedRerunTasks;
+    protected final DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
+    protected boolean expectedContinue;
+    protected boolean expectedOffline;
+    protected boolean expectedRecompileScripts;
+    protected boolean expectedParallelProjectExecution;
+    protected int expectedParallelExecutorCount;
+    protected int expectedMaxWorkersCount = Runtime.getRuntime().availableProcessors();
+    protected boolean expectedConfigureOnDemand;
+    protected boolean expectedContinuous;
+
+    protected void checkConversion(String... args) {
+        actualStartParameter = new StartParameter();
+        actualStartParameter.setCurrentDir(currentDir);
+        commandLineConverter.convert(Arrays.asList(args), actualStartParameter);
+        // We check the params passed to the build factory
+        checkStartParameter(actualStartParameter);
+    }
+
+    protected void checkStartParameter(StartParameter startParameter) {
+        assertEquals(expectedBuildFile, startParameter.getBuildFile());
+        assertEquals(expectedTaskNames, startParameter.getTaskNames());
+        assertEquals(buildProjectDependencies, startParameter.isBuildProjectDependencies());
+        if(expectedCurrentDir != null) {
+            assertEquals(expectedCurrentDir.getAbsoluteFile(), startParameter.getCurrentDir().getAbsoluteFile());
+        }
+        assertEquals(expectedProjectDir, startParameter.getProjectDir());
+        assertEquals(expectedSearchUpwards, startParameter.isSearchUpwards());
+        assertEquals(expectedProjectProperties, startParameter.getProjectProperties());
+        assertEquals(expectedSystemProperties, startParameter.getSystemPropertiesArgs());
+        assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
+        assertEquals(expectedLogLevel, startParameter.getLogLevel());
+        assertEquals(expectedColorOutput, startParameter.isColorOutput());
+        assertEquals(expectedConsoleOutput, startParameter.getConsoleOutput());
+        assertEquals(expectedDryRun, startParameter.isDryRun());
+        assertEquals(expectedShowStackTrace, startParameter.getShowStacktrace());
+        assertEquals(expectedExcludedTasks, startParameter.getExcludedTaskNames());
+        assertEquals(expectedInitScripts, startParameter.getInitScripts());
+        assertEquals(expectedProfile, startParameter.isProfile());
+        assertEquals(expectedContinue, startParameter.isContinueOnFailure());
+        assertEquals(expectedOffline, startParameter.isOffline());
+        assertEquals(expectedRecompileScripts, startParameter.isRecompileScripts());
+        assertEquals(expectedRerunTasks, startParameter.isRerunTasks());
+        assertEquals(expectedRefreshDependencies, startParameter.isRefreshDependencies());
+        assertEquals(expectedProjectCacheDir, startParameter.getProjectCacheDir());
+        assertEquals(expectedParallelExecutorCount, startParameter.getParallelThreadCount());
+        assertEquals(expectedConfigureOnDemand, startParameter.isConfigureOnDemand());
+        assertEquals(expectedMaxWorkersCount, startParameter.getMaxWorkerCount());
+        assertEquals(expectedContinuous, startParameter.isContinuous());
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultBuildCancellationTokenSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultBuildCancellationTokenSpec.groovy
index 8938998..49e09b1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultBuildCancellationTokenSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultBuildCancellationTokenSpec.groovy
@@ -28,7 +28,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         !token.cancellationRequested
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         token.cancellationRequested
@@ -43,7 +43,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         token.addCallback(callback2)
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         token.cancellationRequested
@@ -55,7 +55,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         def token = new DefaultBuildCancellationToken()
 
         def callback = Mock(Runnable)
-        token.doCancel()
+        token.cancel()
 
         when:
         token.addCallback(callback)
@@ -72,7 +72,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         token.addCallback(callback1)
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         token.cancellationRequested
@@ -90,7 +90,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         token.addCallback(callback2)
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         RuntimeException e = thrown()
@@ -115,7 +115,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         token.addCallback(callback3)
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         DefaultMultiCauseException e = thrown()
@@ -136,7 +136,7 @@ class DefaultBuildCancellationTokenSpec extends Specification {
         token.removeCallback(callback)
 
         when:
-        token.doCancel()
+        token.cancel()
 
         then:
         token.cancellationRequested
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.groovy
deleted file mode 100644
index eb9420b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.groovy
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.logging.LogLevel
-import org.gradle.cli.CommandLineArgumentException
-import org.gradle.logging.ConsoleOutput
-import org.gradle.logging.ShowStacktrace
-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 spock.lang.Unroll
-
-import static java.util.Arrays.asList
-import static org.gradle.util.WrapUtil.*
-import static org.hamcrest.Matchers.equalTo
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertThat
-
-public class DefaultCommandLineConverterTest extends Specification {
-    @Rule
-    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
-
-    private TestFile currentDir = testDir.file("current-dir");
-    private File expectedBuildFile;
-    private File expectedGradleUserHome = new BuildLayoutParameters().getGradleUserHomeDir();
-    private File expectedCurrentDir = currentDir;
-    private File expectedProjectDir;
-    private List<String> expectedTaskNames = toList();
-    private Set<String> expectedExcludedTasks = toSet();
-    private boolean buildProjectDependencies = true;
-    private Map<String, String> expectedSystemProperties = new HashMap<String, String>();
-    private Map<String, String> expectedProjectProperties = new HashMap<String, String>();
-    private List<File> expectedInitScripts = new ArrayList<File>();
-    private boolean expectedSearchUpwards = true;
-    private boolean expectedDryRun;
-    private ShowStacktrace expectedShowStackTrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
-    private LogLevel expectedLogLevel = LogLevel.LIFECYCLE;
-    private boolean expectedColorOutput = true;
-    private ConsoleOutput expectedConsoleOutput = ConsoleOutput.Auto;
-    private StartParameter actualStartParameter;
-    private boolean expectedProfile;
-    private File expectedProjectCacheDir;
-    private boolean expectedRefreshDependencies;
-    private boolean expectedRerunTasks;
-    private final DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
-    private boolean expectedContinue;
-    private boolean expectedOffline;
-    private boolean expectedRecompileScripts;
-    private boolean expectedParallelProjectExecution;
-    private int expectedParallelExecutorCount;
-    private int expectedMaxWorkersCount = Runtime.getRuntime().availableProcessors();
-    private boolean expectedConfigureOnDemand;
-
-    @Test
-    public void withoutAnyOptions() {
-        checkConversion();
-    }
-
-    private void checkConversion(String... args) {
-        actualStartParameter = new StartParameter();
-        actualStartParameter.setCurrentDir(currentDir);
-        commandLineConverter.convert(asList(args), actualStartParameter);
-        // We check the params passed to the build factory
-        checkStartParameter(actualStartParameter);
-    }
-
-    private void checkStartParameter(StartParameter startParameter) {
-        assertEquals(expectedBuildFile, startParameter.getBuildFile());
-        assertEquals(expectedTaskNames, startParameter.getTaskNames());
-        assertEquals(buildProjectDependencies, startParameter.isBuildProjectDependencies());
-        assertEquals(expectedCurrentDir.getAbsoluteFile(), startParameter.getCurrentDir().getAbsoluteFile());
-        assertEquals(expectedProjectDir, startParameter.getProjectDir());
-        assertEquals(expectedSearchUpwards, startParameter.isSearchUpwards());
-        assertEquals(expectedProjectProperties, startParameter.getProjectProperties());
-        assertEquals(expectedSystemProperties, startParameter.getSystemPropertiesArgs());
-        assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
-        assertEquals(expectedLogLevel, startParameter.getLogLevel());
-        assertEquals(expectedColorOutput, startParameter.isColorOutput());
-        assertEquals(expectedConsoleOutput, startParameter.getConsoleOutput());
-        assertEquals(expectedDryRun, startParameter.isDryRun());
-        assertEquals(expectedShowStackTrace, startParameter.getShowStacktrace());
-        assertEquals(expectedExcludedTasks, startParameter.getExcludedTaskNames());
-        assertEquals(expectedInitScripts, startParameter.getInitScripts());
-        assertEquals(expectedProfile, startParameter.isProfile());
-        assertEquals(expectedContinue, startParameter.isContinueOnFailure());
-        assertEquals(expectedOffline, startParameter.isOffline());
-        assertEquals(expectedRecompileScripts, startParameter.isRecompileScripts());
-        assertEquals(expectedRerunTasks, startParameter.isRerunTasks());
-        assertEquals(expectedRefreshDependencies, startParameter.isRefreshDependencies());
-        assertEquals(expectedProjectCacheDir, startParameter.getProjectCacheDir());
-        assertEquals(expectedParallelExecutorCount, startParameter.getParallelThreadCount());
-        assertEquals(expectedConfigureOnDemand, startParameter.isConfigureOnDemand());
-        assertEquals(expectedMaxWorkersCount, startParameter.getMaxWorkerCount());
-    }
-
-    @Test
-    public void withSpecifiedGradleUserHomeDirectory() {
-        expectedGradleUserHome = testDir.file("home");
-        checkConversion("-g", expectedGradleUserHome.getAbsolutePath());
-
-        expectedGradleUserHome = currentDir.file("home");
-        checkConversion("-g", "home");
-    }
-
-    @Test
-    public void withSpecifiedProjectCacheDir() {
-        expectedProjectCacheDir = new File(currentDir, ".foo");
-        checkConversion("--project-cache-dir", ".foo");
-    }
-
-    @Test
-    public void withSpecifiedProjectDirectory() {
-        expectedCurrentDir = testDir.file("project-dir");
-        expectedProjectDir = expectedCurrentDir;
-        checkConversion("-p", expectedCurrentDir.getAbsolutePath());
-
-        expectedCurrentDir = currentDir.file("project-dir");
-        expectedProjectDir = expectedCurrentDir;
-        checkConversion("-p", "project-dir");
-    }
-
-    @Test
-    public void withSpecifiedBuildFileName() throws IOException {
-        expectedBuildFile = testDir.file("somename");
-        expectedCurrentDir = expectedBuildFile.getParentFile();
-        expectedProjectDir = expectedCurrentDir;
-        checkConversion("-b", expectedBuildFile.getAbsolutePath());
-
-        expectedBuildFile = currentDir.file("somename");
-        expectedCurrentDir = expectedBuildFile.getParentFile();
-        expectedProjectDir = expectedCurrentDir;
-        checkConversion("-b", "somename");
-    }
-
-    @Test
-    public void withSpecifiedSettingsFileName() throws IOException {
-        File expectedSettingsFile = currentDir.file("somesettings");
-        expectedCurrentDir = expectedSettingsFile.getParentFile();
-
-        checkConversion("-c", "somesettings");
-
-        assertThat(actualStartParameter.getSettingsFile(), equalTo(expectedSettingsFile));
-    }
-
-    @Test
-    public void withInitScripts() {
-        File script1 = currentDir.file("init1.gradle");
-        expectedInitScripts.add(script1);
-        checkConversion("-Iinit1.gradle");
-
-        File script2 = currentDir.file("init2.gradle");
-        expectedInitScripts.add(script2);
-        checkConversion("-Iinit1.gradle", "-Iinit2.gradle");
-    }
-
-    @Test
-    public void withSystemProperties() {
-        final String prop1 = "gradle.prop1";
-        final String valueProp1 = "value1";
-        final String prop2 = "gradle.prop2";
-        final String valueProp2 = "value2";
-        expectedSystemProperties = toMap(prop1, valueProp1);
-        expectedSystemProperties.put(prop2, valueProp2);
-        checkConversion("-D", prop1 + "=" + valueProp1, "-D", prop2 + "=" + valueProp2);
-    }
-
-    @Test
-    public void withSpecifiedGradleUserHomeDirectoryBySystemProperty() {
-        expectedGradleUserHome = testDir.file("home");
-        String propName = "gradle.user.home";
-        String propValue = expectedGradleUserHome.getAbsolutePath();
-        expectedSystemProperties = toMap(propName, propValue);
-        checkConversion("-D", propName + "=" + propValue);
-    }
-
-    @Test
-    public void privilegeCmdLineOptionOverSystemPrefForGradleUserHome() {
-        expectedGradleUserHome = testDir.file("home");
-        String propName = "gradle.user.home";
-        String propValue = "home2";
-        expectedSystemProperties = toMap(propName, propValue);
-        checkConversion("-D", propName + "=" + propValue, "-g", expectedGradleUserHome.getAbsolutePath());
-    }
-
-    @Test
-    public void withStartProperties() {
-        final String prop1 = "prop1";
-        final String valueProp1 = "value1";
-        final String prop2 = "prop2";
-        final String valueProp2 = "value2";
-        expectedProjectProperties = toMap(prop1, valueProp1);
-        expectedProjectProperties.put(prop2, valueProp2);
-        checkConversion("-P", prop1 + "=" + valueProp1, "-P", prop2 + "=" + valueProp2);
-    }
-
-    @Test
-    public void withTaskNames() {
-        expectedTaskNames = toList("a", "b");
-        checkConversion("a", "b");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withUnknownCacheFlags() {
-        checkConversion("-C", "unknown");
-    }
-
-    @Test
-    public void withSearchUpwardsFlagSet() {
-        expectedSearchUpwards = false;
-        checkConversion("-u");
-    }
-
-    @Test
-    public void withShowFullStacktrace() {
-        expectedShowStackTrace = ShowStacktrace.ALWAYS_FULL;
-        checkConversion("-S");
-    }
-
-    @Test
-    public void withShowStacktrace() {
-        expectedShowStackTrace = ShowStacktrace.ALWAYS;
-        checkConversion("-s");
-    }
-
-    @Test
-    public void withRerunTasks() {
-        expectedRerunTasks = true;
-        checkConversion("--rerun-tasks");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withShowStacktraceAndShowFullStacktraceShouldThrowCommandLineArgumentEx() {
-        checkConversion("-sf");
-    }
-
-    @Test
-    public void withDryRunFlagSet() {
-        expectedDryRun = true;
-        checkConversion("-m");
-    }
-
-    @Test
-    public void withExcludeTask() {
-        expectedExcludedTasks.add("excluded");
-        checkConversion("-x", "excluded");
-        expectedExcludedTasks.add("excluded2");
-        checkConversion("-x", "excluded", "-x", "excluded2");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withEmbeddedScriptAndConflictingNoSearchUpwardsOption() {
-        checkConversion("-e", "someScript", "-u", "clean");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withEmbeddedScriptAndConflictingSpecifyBuildFileOption() {
-        checkConversion("-e", "someScript", "-bsomeFile", "clean");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withEmbeddedScriptAndConflictingSpecifySettingsFileOption() {
-        checkConversion("-e", "someScript", "-csomeFile", "clean");
-    }
-
-    @Test
-    public void withNoProjectDependencyRebuild() {
-        buildProjectDependencies = false;
-        checkConversion("-a");
-    }
-
-    @Test
-    public void withQuietLoggingOptions() {
-        expectedLogLevel = LogLevel.QUIET;
-        checkConversion("-q");
-    }
-
-    @Test
-    public void withInfoLoggingOptions() {
-        expectedLogLevel = LogLevel.INFO;
-        checkConversion("-i");
-    }
-
-    @Test
-    public void withDebugLoggingOptions() {
-        expectedLogLevel = LogLevel.DEBUG;
-        checkConversion("-d");
-    }
-
-    @Test
-    public void withNoColor() {
-        expectedColorOutput = false;
-        expectedConsoleOutput = ConsoleOutput.Plain;
-        checkConversion("--no-color");
-    }
-
-    @Test
-    public void withColor() {
-        expectedConsoleOutput = ConsoleOutput.Rich;
-        checkConversion("--console", "rich");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withLowerPParameterWithoutArgument() {
-        checkConversion("-p");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withAParameterWithoutArgument() {
-        checkConversion("-A");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withUpperAAndLowerAParameter() {
-        checkConversion("-a", "-Atask1");
-    }
-
-    @Test
-    public void withProfile() {
-        expectedProfile = true;
-        checkConversion("--profile");
-    }
-
-    @Test
-    public void withContinue() {
-        expectedContinue = true;
-        checkConversion("--continue");
-    }
-
-    @Test
-    public void withOffline() {
-        expectedOffline = true;
-        checkConversion("--offline");
-        checkConversion("-offline");
-    }
-
-    @Test
-    public void withRefreshDependencies() {
-        expectedRefreshDependencies = true;
-        checkConversion("--refresh-dependencies");
-        checkConversion("-refresh-dependencies");
-    }
-
-    @Test
-    public void withRecompileScripts() {
-        expectedRecompileScripts = true;
-        checkConversion("--recompile-scripts");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withUnknownOption() {
-        checkConversion("--unknown");
-    }
-
-    @Test
-    public void withTaskAndTaskOption() {
-        expectedTaskNames = toList("someTask", "--some-task-option");
-        checkConversion("someTask", "--some-task-option");
-    }
-
-    @Test
-    public void withParallelExecutor() {
-        expectedParallelProjectExecution = true;
-        expectedParallelExecutorCount = expectedMaxWorkersCount;
-        checkConversion("--parallel");
-    }
-
-    @Test
-    public void withParallelExecutorThreads() {
-        expectedParallelProjectExecution = true;
-        expectedMaxWorkersCount = expectedParallelExecutorCount = 5;
-        checkConversion("--parallel-threads", "5");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withInvalidParallelExecutorThreads() {
-        checkConversion("--parallel-threads", "foo");
-    }
-
-
-    @Test
-    public void withMaxWorkers() {
-        expectedMaxWorkersCount = 5;
-        checkConversion("--max-workers", "5");
-    }
-
-    @Test(expected = CommandLineArgumentException.class)
-    public void withInvalidMaxWorkers() {
-        checkConversion("--max-workers", "foo");
-    }
-
-
-    @Test
-    public void withConfigureOnDemand() {
-        expectedConfigureOnDemand = true;
-        checkConversion("--configure-on-demand");
-    }
-
-    final static int NUM_OF_PROCS = Runtime.getRuntime().availableProcessors()
-    final static int N = 3
-    final static int M = 5
-
-    @Test
-    @Unroll("check combinations using #args")
-    public void checkCombinationsOfWorkersAndParallelOptions(List args, int maxWorkers, int parallelThreads, boolean isParallel) {
-        given:
-        expectedMaxWorkersCount = maxWorkers
-        expectedParallelExecutorCount = parallelThreads
-        expectedParallelProjectExecution = isParallel
-
-        expect:
-        checkConversion(*args)
-
-        where:
-        args                                          | maxWorkers   | parallelThreads | isParallel
-        []                                            | NUM_OF_PROCS | 0               | false
-        ["--parallel"]                                | NUM_OF_PROCS | NUM_OF_PROCS    | true
-        ["--parallel-threads=$N"]                     | N            | N               | true
-        ["--max-workers=$N"]                          | N            | 0               | false
-        ["--parallel", "--max-workers=$N"]            | N            | N               | true
-        ["--parallel-threads=$N", "--max-workers=$M"] | M            | M               | true
-        ["--max-workers=$N", "--parallel-threads=$M"] | M            | M               | true
-        ["--parallel-threads=-1"]                     | NUM_OF_PROCS | NUM_OF_PROCS    | true
-        ["--parallel-threads=0"]                      | NUM_OF_PROCS | 0               | false
-        ["--parallel-threads=1"]                      | 1            | 1               | true
-        ["--parallel", "--max-workers=1"]             | 1            | 1               | true
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
new file mode 100644
index 0000000..3c9ba70
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging.LogLevel;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.logging.ConsoleOutput;
+import org.gradle.logging.ShowStacktrace;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.gradle.util.WrapUtil.toList;
+import static org.gradle.util.WrapUtil.toMap;
+
+public class DefaultCommandLineConverterTest extends CommandLineConverterTestSupport {
+    @Rule
+    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+
+    public DefaultCommandLineConverterTest() {
+        super();
+        currentDir = testDir.file("current-dir");
+        expectedCurrentDir = currentDir;
+    }
+
+    @Test
+    public void withoutAnyOptions() {
+        checkConversion();
+    }
+
+    @Test
+    public void withSpecifiedGradleUserHomeDirectory() {
+        expectedGradleUserHome = testDir.file("home");
+        checkConversion("-g", expectedGradleUserHome.getAbsolutePath());
+
+        expectedGradleUserHome = currentDir.file("home");
+        checkConversion("-g", "home");
+    }
+
+    @Test
+    public void withSpecifiedProjectCacheDir() {
+        expectedProjectCacheDir = new File(currentDir, ".foo");
+        checkConversion("--project-cache-dir", ".foo");
+    }
+
+    @Test
+    public void withSpecifiedProjectDirectory() {
+        expectedCurrentDir = testDir.file("project-dir");
+        expectedProjectDir = expectedCurrentDir;
+        checkConversion("-p", expectedCurrentDir.getAbsolutePath());
+
+        expectedCurrentDir = currentDir.file("project-dir");
+        expectedProjectDir = expectedCurrentDir;
+        checkConversion("-p", "project-dir");
+    }
+
+    @Test
+    public void withSpecifiedBuildFileName() throws IOException {
+        expectedBuildFile = testDir.file("somename");
+        expectedCurrentDir = expectedBuildFile.getParentFile();
+        expectedProjectDir = expectedCurrentDir;
+        checkConversion("-b", expectedBuildFile.getAbsolutePath());
+
+        expectedBuildFile = currentDir.file("somename");
+        expectedCurrentDir = expectedBuildFile.getParentFile();
+        expectedProjectDir = expectedCurrentDir;
+        checkConversion("-b", "somename");
+    }
+
+    @Test
+    public void withSpecifiedSettingsFileName() throws IOException {
+        File expectedSettingsFile = currentDir.file("somesettings");
+        expectedCurrentDir = expectedSettingsFile.getParentFile();
+
+        checkConversion("-c", "somesettings");
+
+        Assert.assertThat(actualStartParameter.getSettingsFile(), Matchers.equalTo((File) expectedSettingsFile));
+    }
+
+    @Test
+    public void withInitScripts() {
+        File script1 = currentDir.file("init1.gradle");
+        expectedInitScripts.add(script1);
+        checkConversion("-Iinit1.gradle");
+
+        File script2 = currentDir.file("init2.gradle");
+        expectedInitScripts.add(script2);
+        checkConversion("-Iinit1.gradle", "-Iinit2.gradle");
+    }
+
+    @Test
+    public void withSystemProperties() {
+        final String prop1 = "gradle.prop1";
+        final String valueProp1 = "value1";
+        final String prop2 = "gradle.prop2";
+        final String valueProp2 = "value2";
+        expectedSystemProperties = toMap(prop1, valueProp1);
+        expectedSystemProperties.put(prop2, valueProp2);
+        checkConversion("-D", prop1 + "=" + valueProp1, "-D", prop2 + "=" + valueProp2);
+    }
+
+    @Test
+    public void withSpecifiedGradleUserHomeDirectoryBySystemProperty() {
+        expectedGradleUserHome = testDir.file("home");
+        String propName = "gradle.user.home";
+        String propValue = expectedGradleUserHome.getAbsolutePath();
+        expectedSystemProperties = toMap(propName, propValue);
+        checkConversion("-D", propName + "=" + propValue);
+    }
+
+    @Test
+    public void privilegeCmdLineOptionOverSystemPrefForGradleUserHome() {
+        expectedGradleUserHome = testDir.file("home");
+        String propName = "gradle.user.home";
+        String propValue = "home2";
+        expectedSystemProperties = toMap(propName, propValue);
+        checkConversion("-D", propName + "=" + propValue, "-g", expectedGradleUserHome.getAbsolutePath());
+    }
+
+    @Test
+    public void withStartProperties() {
+        final String prop1 = "prop1";
+        final String valueProp1 = "value1";
+        final String prop2 = "prop2";
+        final String valueProp2 = "value2";
+        expectedProjectProperties = toMap(prop1, valueProp1);
+        expectedProjectProperties.put(prop2, valueProp2);
+        checkConversion("-P", prop1 + "=" + valueProp1, "-P", prop2 + "=" + valueProp2);
+    }
+
+    @Test
+    public void withTaskNames() {
+        expectedTaskNames = toList("a", "b");
+        checkConversion("a", "b");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUnknownCacheFlags() {
+        checkConversion("-C", "unknown");
+    }
+
+    @Test
+    public void withSearchUpwardsFlagSet() {
+        expectedSearchUpwards = false;
+        checkConversion("-u");
+    }
+
+    @Test
+    public void withShowFullStacktrace() {
+        expectedShowStackTrace = ShowStacktrace.ALWAYS_FULL;
+        checkConversion("-S");
+    }
+
+    @Test
+    public void withShowStacktrace() {
+        expectedShowStackTrace = ShowStacktrace.ALWAYS;
+        checkConversion("-s");
+    }
+
+    @Test
+    public void withRerunTasks() {
+        expectedRerunTasks = true;
+        checkConversion("--rerun-tasks");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withShowStacktraceAndShowFullStacktraceShouldThrowCommandLineArgumentEx() {
+        checkConversion("-sf");
+    }
+
+    @Test
+    public void withDryRunFlagSet() {
+        expectedDryRun = true;
+        checkConversion("-m");
+    }
+
+    @Test
+    public void withExcludeTask() {
+        expectedExcludedTasks.add("excluded");
+        checkConversion("-x", "excluded");
+        expectedExcludedTasks.add("excluded2");
+        checkConversion("-x", "excluded", "-x", "excluded2");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingNoSearchUpwardsOption() {
+        checkConversion("-e", "someScript", "-u", "clean");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingSpecifyBuildFileOption() {
+        checkConversion("-e", "someScript", "-bsomeFile", "clean");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingSpecifySettingsFileOption() {
+        checkConversion("-e", "someScript", "-csomeFile", "clean");
+    }
+
+    @Test
+    public void withNoProjectDependencyRebuild() {
+        buildProjectDependencies = false;
+        checkConversion("-a");
+    }
+
+    @Test
+    public void withQuietLoggingOptions() {
+        expectedLogLevel = LogLevel.QUIET;
+        checkConversion("-q");
+    }
+
+    @Test
+    public void withInfoLoggingOptions() {
+        expectedLogLevel = LogLevel.INFO;
+        checkConversion("-i");
+    }
+
+    @Test
+    public void withDebugLoggingOptions() {
+        expectedLogLevel = LogLevel.DEBUG;
+        checkConversion("-d");
+    }
+
+    @Test
+    public void withNoColor() {
+        expectedColorOutput = false;
+        expectedConsoleOutput = ConsoleOutput.Plain;
+        checkConversion("--no-color");
+    }
+
+    @Test
+    public void withColor() {
+        expectedConsoleOutput = ConsoleOutput.Rich;
+        checkConversion("--console", "rich");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withLowerPParameterWithoutArgument() {
+        checkConversion("-p");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withAParameterWithoutArgument() {
+        checkConversion("-A");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUpperAAndLowerAParameter() {
+        checkConversion("-a", "-Atask1");
+    }
+
+    @Test
+    public void withProfile() {
+        expectedProfile = true;
+        checkConversion("--profile");
+    }
+
+    @Test
+    public void withContinue() {
+        expectedContinue = true;
+        checkConversion("--continue");
+    }
+
+    @Test
+    public void withOffline() {
+        expectedOffline = true;
+        checkConversion("--offline");
+        checkConversion("-offline");
+    }
+
+    @Test
+    public void withRefreshDependencies() {
+        expectedRefreshDependencies = true;
+        checkConversion("--refresh-dependencies");
+        checkConversion("-refresh-dependencies");
+    }
+
+    @Test
+    public void withRecompileScripts() {
+        expectedRecompileScripts = true;
+        checkConversion("--recompile-scripts");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUnknownOption() {
+        checkConversion("--unknown");
+    }
+
+    @Test
+    public void withTaskAndTaskOption() {
+        expectedTaskNames = toList("someTask", "--some-task-option");
+        checkConversion("someTask", "--some-task-option");
+    }
+
+    @Test
+    public void withParallelExecutor() {
+        expectedParallelProjectExecution = true;
+        expectedParallelExecutorCount = expectedMaxWorkersCount;
+        checkConversion("--parallel");
+    }
+
+    @Test
+    public void withParallelExecutorThreads() {
+        expectedParallelProjectExecution = true;
+        expectedMaxWorkersCount = expectedParallelExecutorCount = 5;
+        checkConversion("--parallel-threads", "5");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withInvalidParallelExecutorThreads() {
+        checkConversion("--parallel-threads", "foo");
+    }
+
+    @Test
+    public void withMaxWorkers() {
+        expectedMaxWorkersCount = 5;
+        checkConversion("--max-workers", "5");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withInvalidMaxWorkers() {
+        checkConversion("--max-workers", "foo");
+    }
+
+    @Test
+    public void withConfigureOnDemand() {
+        expectedConfigureOnDemand = true;
+        checkConversion("--configure-on-demand");
+    }
+
+    @Test
+    public void withContinuous() {
+        expectedContinuous = true;
+        checkConversion("--continuous");
+    }
+
+    @Test
+    public void withContinuousShortFlag() {
+        expectedContinuous = true;
+        checkConversion("-t");
+    }
+
+}
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 ca3e369..6f9c533 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
@@ -52,7 +52,7 @@ class DefaultGradleLauncherFactoryTest extends Specification {
         def launcher = factory.newInstance(startParameter)
         launcher.gradle.parent == null
         launcher.gradle.services.get(BuildRequestMetaData) instanceof DefaultBuildRequestMetaData
-        launcher.gradle.services.get(BuildCancellationToken) instanceof FixedBuildCancellationToken
+        launcher.gradle.services.get(BuildCancellationToken) instanceof DefaultBuildCancellationToken
         launcher.gradle.services.get(BuildEventConsumer) instanceof NoOpBuildEventConsumer
     }
 
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 fe005db..777661a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -29,6 +29,9 @@ 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.internal.Factory;
+import org.gradle.internal.progress.BuildOperationExecutor;
+import org.gradle.internal.progress.BuildOperationType;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.JUnit4GroovyMockery;
@@ -49,11 +52,12 @@ import java.io.IOException;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class DefaultGradleLauncherTest {
     private BuildLoader buildLoaderMock;
-    private InitScriptHandler initscriptHandlerMock;
+    private InitScriptHandler initScriptHandlerMock;
     private SettingsHandler settingsHandlerMock;
     private BuildConfigurer buildConfigurerMock;
     private DefaultProject expectedRootProject;
@@ -80,12 +84,13 @@ public class DefaultGradleLauncherTest {
     private ModelConfigurationListener modelListenerMock = context.mock(ModelConfigurationListener.class);
     private TasksCompletionListener tasksCompletionListener = context.mock(TasksCompletionListener.class);
     private BuildCompletionListener buildCompletionListener = context.mock(BuildCompletionListener.class);
+    private BuildOperationExecutor buildOperationExecutor = new TestBuildOperationExecutor();
     private Closeable buildServices = context.mock(Closeable.class);
     public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() {
-        initscriptHandlerMock = context.mock(InitScriptHandler.class);
+        initScriptHandlerMock = context.mock(InitScriptHandler.class);
         settingsHandlerMock = context.mock(SettingsHandler.class);
         settingsMock = context.mock(SettingsInternal.class);
         taskExecuterMock = context.mock(TaskGraphExecuter.class);
@@ -100,10 +105,10 @@ public class DefaultGradleLauncherTest {
         File expectedCurrentDir = new File(expectedRootDir, "currentDir");
 
         expectedRootProjectDescriptor = new DefaultProjectDescriptor(null, "someName", new File("somedir"), new DefaultProjectDescriptorRegistry(),
-                TestFiles.resolver(expectedRootDir));
+            TestFiles.resolver(expectedRootDir));
         expectedRootProject = TestUtil.createRootProject(expectedRootDir);
         expectedDefaultProjectDescriptor = new DefaultProjectDescriptor(null, "default", new File("default"), new DefaultProjectDescriptorRegistry(),
-                TestFiles.resolver(expectedCurrentDir));
+            TestFiles.resolver(expectedCurrentDir));
         expectedCurrentProject = TestUtil.createRootProject(expectedCurrentDir);
 
         expectedStartParams = new StartParameter();
@@ -111,9 +116,10 @@ public class DefaultGradleLauncherTest {
         expectedStartParams.setSearchUpwards(expectedSearchUpwards);
         expectedStartParams.setGradleUserHomeDir(tmpDir.createDir("gradleUserHome"));
 
-        gradleLauncher = new DefaultGradleLauncher(gradleMock, initscriptHandlerMock, settingsHandlerMock,
-                buildLoaderMock, buildConfigurerMock, buildBroadcaster, exceptionAnalyserMock, loggingManagerMock,
-                modelListenerMock, tasksCompletionListener, buildExecuter, buildCompletionListener, buildServices);
+        gradleLauncher = new DefaultGradleLauncher(gradleMock, initScriptHandlerMock, settingsHandlerMock,
+            buildLoaderMock, buildConfigurerMock, exceptionAnalyserMock, loggingManagerMock, buildBroadcaster,
+            modelListenerMock, tasksCompletionListener, buildCompletionListener, buildOperationExecutor, buildExecuter,
+            buildServices);
 
         context.checking(new Expectations() {
             {
@@ -178,9 +184,12 @@ public class DefaultGradleLauncherTest {
             will(returnValue(transformedException));
             one(buildBroadcaster).buildFinished(with(any(BuildResult.class)));
         }});
-        BuildResult buildResult = gradleLauncher.getBuildAnalysis();
-        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
-        assertThat((RuntimeException) buildResult.getFailure(), sameInstance(transformedException));
+        try {
+            gradleLauncher.getBuildAnalysis();
+            fail();
+        } catch (ReportedException e) {
+            assertThat((RuntimeException) e.getCause(), sameInstance(transformedException));
+        }
     }
 
     @Test
@@ -224,8 +233,12 @@ public class DefaultGradleLauncherTest {
             one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
         }});
 
-        BuildResult buildResult = gradleLauncher.run();
-        assertThat(buildResult.getFailure(), sameInstance((Throwable) transformedException));
+        try {
+            gradleLauncher.run();
+            fail();
+        } catch (ReportedException e) {
+            assertThat((RuntimeException) e.getCause(), sameInstance(transformedException));
+        }
     }
 
     @Test
@@ -247,8 +260,12 @@ public class DefaultGradleLauncherTest {
             one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
         }});
 
-        BuildResult buildResult = gradleLauncher.run();
-        assertThat(buildResult.getFailure(), sameInstance((Throwable) transformedException));
+        try {
+            gradleLauncher.run();
+            fail();
+        } catch (ReportedException e) {
+            assertThat((RuntimeException) e.getCause(), sameInstance(transformedException));
+        }
     }
 
     @Test
@@ -270,7 +287,7 @@ public class DefaultGradleLauncherTest {
 
     private void expectInitScriptsExecuted() {
         context.checking(new Expectations() {{
-            one(initscriptHandlerMock).executeScripts(gradleMock);
+            one(initScriptHandlerMock).executeScripts(gradleMock);
         }});
     }
 
@@ -285,13 +302,15 @@ public class DefaultGradleLauncherTest {
     }
 
     private void expectBuildListenerCallbacks() {
-        context.checking(new Expectations() {{
-            one(buildBroadcaster).buildStarted(gradleMock);
-            one(buildBroadcaster).projectsLoaded(gradleMock);
-            one(buildBroadcaster).projectsEvaluated(gradleMock);
-            one(buildBroadcaster).buildFinished(with(result(nullValue(Throwable.class))));
-            one(modelListenerMock).onConfigure(gradleMock);
-        }});
+        context.checking(new Expectations() {
+            {
+                one(buildBroadcaster).buildStarted(gradleMock);
+                one(buildBroadcaster).projectsLoaded(gradleMock);
+                one(buildBroadcaster).projectsEvaluated(gradleMock);
+                one(buildBroadcaster).buildFinished(with(result(nullValue(Throwable.class))));
+                one(modelListenerMock).onConfigure(gradleMock);
+            }
+        });
     }
 
     private void expectDagBuilt() {
@@ -334,4 +353,21 @@ public class DefaultGradleLauncherTest {
             }
         };
     }
+
+    private static class TestBuildOperationExecutor implements BuildOperationExecutor {
+        @Override
+        public Object getCurrentOperationId() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T run(Object id, BuildOperationType operationType, Factory<T> factory) {
+            return factory.create();
+        }
+
+        @Override
+        public void run(Object id, BuildOperationType operationType, Runnable action) {
+            action.run();
+        }
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ParallelOptionsCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ParallelOptionsCommandLineConverterTest.groovy
new file mode 100644
index 0000000..5deaf7c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ParallelOptionsCommandLineConverterTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Unroll
+
+class ParallelOptionsCommandLineConverterTest extends Specification {
+
+    final static int NUM_OF_PROCS = Runtime.getRuntime().availableProcessors()
+    final static int N = 3
+    final static int M = 5
+
+    @Unroll("check combinations using #args")
+    public void checkCombinationsOfWorkersAndParallelOptions(List args, int maxWorkers, int parallelThreads, boolean isParallel) {
+        given:
+        CommandLineConverterTestSupport commandLineTester = new CommandLineConverterTestSupport()
+
+        commandLineTester.expectedMaxWorkersCount = maxWorkers
+        commandLineTester.expectedParallelExecutorCount = parallelThreads
+        commandLineTester.expectedParallelProjectExecution = isParallel
+
+        expect:
+        commandLineTester.checkConversion(*args)
+
+        where:
+        args                                          | maxWorkers   | parallelThreads | isParallel
+        []                                            | NUM_OF_PROCS | 0               | false
+        ["--parallel"]                                | NUM_OF_PROCS | NUM_OF_PROCS    | true
+        ["--parallel-threads=$N"]                     | N            | N               | true
+        ["--max-workers=$N"]                          | N            | 0               | false
+        ["--parallel", "--max-workers=$N"]            | N            | N               | true
+        ["--parallel-threads=$N", "--max-workers=$M"] | M            | M               | true
+        ["--max-workers=$N", "--parallel-threads=$M"] | M            | M               | true
+        ["--parallel-threads=-1"]                     | NUM_OF_PROCS | NUM_OF_PROCS    | true
+        ["--parallel-threads=0"]                      | NUM_OF_PROCS | 0               | false
+        ["--parallel-threads=1"]                      | 1            | 1               | true
+        ["--parallel", "--max-workers=1"]             | 1            | 1               | true
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiterTest.groovy
new file mode 100644
index 0000000..06427ac
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileSystemChangeWaiterTest.groovy
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch
+
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileSystemSubset
+import org.gradle.initialization.DefaultBuildCancellationToken
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Unroll
+
+import java.util.concurrent.atomic.AtomicLong
+import java.util.concurrent.atomic.AtomicReference
+
+ at Requires(TestPrecondition.JDK7_OR_LATER)
+class DefaultFileSystemChangeWaiterTest extends ConcurrentSpec {
+
+    @Rule
+    TestNameTestDirectoryProvider testDirectory
+
+    def "can wait for filesystem change"() {
+        when:
+        def w = new DefaultFileSystemChangeWaiter(executorFactory, new DefaultFileWatcherFactory(executorFactory))
+        def f = FileSystemSubset.builder().add(testDirectory.testDirectory).build()
+        def c = new DefaultBuildCancellationToken()
+
+        start {
+            w.wait(f, c) {
+                instant.notified
+            }
+            instant.done
+        }
+
+        then:
+        waitFor.notified
+
+        when:
+        testDirectory.file("new") << "change"
+
+        then:
+        waitFor.done
+    }
+
+    def "escapes on cancel"() {
+        when:
+        def w = new DefaultFileSystemChangeWaiter(executorFactory, new DefaultFileWatcherFactory(executorFactory))
+        def f = FileSystemSubset.builder().add(testDirectory.testDirectory).build()
+        def c = new DefaultBuildCancellationToken()
+
+        start {
+            w.wait(f, c) {
+                instant.notified
+            }
+            instant.done
+        }
+
+        then:
+        waitFor.notified
+
+        when:
+        c.cancel()
+
+        then:
+        waitFor.done
+    }
+
+    def "escapes on exception"() {
+        given:
+        def onErrorReference = new AtomicReference<Action>()
+        def fileWatcherFactory = Mock(FileWatcherFactory) {
+            watch(_, _, _) >> { FileSystemSubset systemSubset, Action onError, FileWatcherListener listener ->
+                onErrorReference.set(onError)
+                Mock(FileWatcher)
+            }
+
+        }
+        when:
+        def w = new DefaultFileSystemChangeWaiter(executorFactory, fileWatcherFactory)
+        def f = FileSystemSubset.builder().add(testDirectory.testDirectory).build()
+        def c = new DefaultBuildCancellationToken()
+
+        start {
+            try {
+                w.wait(f, c) {
+                    instant.notified
+                }
+            } catch (Exception e) {
+                instant.done
+            }
+        }
+
+        then:
+        waitFor.notified
+
+        when:
+        onErrorReference.get().execute(new Exception("Exception in file watching"))
+
+        then:
+        waitFor.done
+    }
+
+    @Unroll
+    def "waits until there is a quiet period - #description"(String description, Closure fileChanger) {
+        when:
+        def quietPeriod = 1000L
+        def w = new DefaultFileSystemChangeWaiter(executorFactory, new DefaultFileWatcherFactory(executorFactory), quietPeriod)
+        def f = FileSystemSubset.builder().add(testDirectory.testDirectory).build()
+        def c = new DefaultBuildCancellationToken()
+        def testfile = testDirectory.file("testfile")
+
+        start {
+            w.wait(f, c) {
+                instant.notified
+            }
+            instant.done
+        }
+
+        then:
+        waitFor.notified
+
+        when:
+        def lastChangeRef = new AtomicLong(0)
+        fileChanger(instant, testfile, lastChangeRef)
+
+        then:
+        waitFor.done
+        lastChangeRef.get() != 0
+        System.currentTimeMillis() - lastChangeRef.get() >= quietPeriod
+
+        where:
+        description            | fileChanger
+        'append and close'     | this.&changeByAppendingAndClosing
+        'append and keep open' | this.&changeByAppendingAndKeepingFileOpen
+    }
+
+    private void changeByAppendingAndClosing(instant, testfile, lastChangeRef) {
+        for (int i = 0; i < 10; i++) {
+            instant.assertNotReached('done')
+            testfile << "change"
+            lastChangeRef.set(System.currentTimeMillis())
+            sleep(50)
+        }
+    }
+
+    private void changeByAppendingAndKeepingFileOpen(instant, testfile, lastChangeRef) {
+        testfile.withPrintWriter { PrintWriter out ->
+            for (int i = 0; i < 10; i++) {
+                instant.assertNotReached('done')
+                out.println("change")
+                out.flush()
+                lastChangeRef.set(System.currentTimeMillis())
+                sleep(50)
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryNonJava7Test.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryNonJava7Test.groovy
new file mode 100644
index 0000000..8a26bcf
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryNonJava7Test.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch
+
+import org.gradle.api.JavaVersion
+import org.gradle.internal.concurrent.ExecutorFactory
+import spock.lang.Specification
+
+class DefaultFileWatcherFactoryNonJava7Test extends Specification {
+
+    def "throws"() {
+        when:
+        new DefaultFileWatcherFactory(JavaVersion.VERSION_1_6, getClass().classLoader, Mock(ExecutorFactory)).createFileWatcherFactory()
+
+        then:
+        thrown UnsupportedOperationException
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryTest.groovy
new file mode 100644
index 0000000..4357b12
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/DefaultFileWatcherFactoryTest.groovy
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch
+
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileSystemSubset
+import org.gradle.internal.Pair
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.internal.NativeServicesTestFixture
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import org.spockframework.lang.ConditionBlock
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+import spock.util.concurrent.BlockingVariable
+import spock.util.concurrent.PollingConditions
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+
+ at Requires(TestPrecondition.JDK7_OR_LATER)
+class DefaultFileWatcherFactoryTest extends Specification {
+
+    @Rule
+    public final TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+    FileWatcherFactory fileWatcherFactory
+    long waitForEventsMillis = 3500L
+
+    @AutoCleanup("stop")
+    FileWatcher fileWatcher
+    private PollingConditions poll = new PollingConditions()
+
+    Throwable thrownInWatchExecution
+    Action<? super Throwable> onError = {
+        thrownInWatchExecution = it
+    }
+
+    FileSystemSubset fileSystemSubset
+
+    void setup() {
+        NativeServicesTestFixture.initialize()
+        fileWatcherFactory = new DefaultFileWatcherFactory(new DefaultExecutorFactory())
+        fileSystemSubset = FileSystemSubset.builder().add(testDir.testDirectory).build()
+    }
+
+    void cleanup() {
+        fileWatcher?.stop()
+        fileWatcherFactory?.stop()
+        if (thrownInWatchExecution) {
+            throw new ExecutionException("Exception was catched in executing watch", thrownInWatchExecution)
+        }
+    }
+
+    def "watch service should notify of new files"() {
+        given:
+        def listener = Mock(FileWatcherListener)
+        def listenerCalledLatch = new CountDownLatch(1)
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError, listener)
+        File createdFile = testDir.file("newfile.txt")
+        createdFile.text = "Hello world"
+        waitOn(listenerCalledLatch)
+        then:
+        (1.._) * listener.onChange(_, _) >> { FileWatcher watcher, FileWatcherEvent event ->
+            handleEvent(event, listenerCalledLatch)
+        }
+        0 * _._
+    }
+
+    def "watch service should notify of new files in subdirectories"() {
+        given:
+        def listener = Mock(FileWatcherListener)
+        def listenerCalledLatch = new CountDownLatch(1)
+        def listenerCalledLatch2 = new CountDownLatch(1)
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError, listener)
+        def subdir = testDir.createDir("subdir")
+        subdir.createFile("somefile").text = "Hello world"
+        waitOn(listenerCalledLatch)
+        then:
+        (1.._) * listener.onChange(_, _) >> { FileWatcher watcher, FileWatcherEvent event ->
+            handleEvent(event, listenerCalledLatch)
+        }
+        0 * _._
+        when:
+        subdir.file('someotherfile').text = "Hello world"
+        waitOn(listenerCalledLatch2)
+        then:
+        (1.._) * listener.onChange(_, _) >> { FileWatcher watcher, FileWatcherEvent event ->
+            handleEvent(event, listenerCalledLatch2)
+        }
+        0 * _._
+    }
+
+    def handleEvent(FileWatcherEvent event, CountDownLatch listenerCalledLatch) {
+        //println "event: $event"
+        listenerCalledLatch.countDown()
+    }
+
+    def "watch service should register to watch subdirs at startup"() {
+        given:
+        def listener = Mock(FileWatcherListener)
+        def subdir = testDir.createDir("subdir")
+        def listenerCalledLatch = new CountDownLatch(1)
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError, listener)
+        subdir.createFile("somefile").text = "Hello world"
+        waitOn(listenerCalledLatch)
+        then:
+        (1.._) * listener.onChange(_, _) >> { FileWatcher watcher, FileWatcherEvent event ->
+            handleEvent(event, listenerCalledLatch)
+        }
+    }
+
+    def "listener can terminate watcher"() {
+        given:
+        def block = this.<List<Boolean>> blockingVar()
+
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError) { watcher, event ->
+            def vals = [watcher.running]
+            watcher.stop()
+            block.set(vals << watcher.running)
+        }
+
+        and:
+        testDir.file("new") << "new"
+
+        then:
+        block.get() == [true, false]
+        !fileWatcher.running
+    }
+
+    def "observer can terminate watcher"() {
+        given:
+        def eventLatch = new CountDownLatch(1)
+        def stopLatch = new CountDownLatch(1)
+        def result = this.<Boolean> blockingVar()
+
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError) { watcher, event ->
+            eventLatch.countDown()
+            waitOn(stopLatch)
+            result.set(watcher.running)
+        }
+
+        and:
+        testDir.file("new") << "new"
+
+        then:
+        waitOn(eventLatch)
+        fileWatcher.stop()
+        stopLatch.countDown()
+        !result.get()
+    }
+
+    def "can interrupt watcher"() {
+        given:
+        def watcherThread = this.<Thread> blockingVar()
+
+        when:
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError) { watcher, event ->
+            watcherThread.set(Thread.currentThread())
+        }
+
+        and:
+        testDir.file("new") << "new"
+
+        then:
+        watcherThread.get().interrupt()
+        await { assert !fileWatcher.running }
+    }
+
+    def "watcher can detects all files added to watched directory"() {
+        when:
+        def eventReceivedLatch = new CountDownLatch(1)
+        def filesAddedLatch = new CountDownLatch(1)
+        def totalLatch = new CountDownLatch(10)
+
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError) { watcher, event ->
+            eventReceivedLatch.countDown()
+            filesAddedLatch.await()
+            totalLatch.countDown()
+        }
+
+        testDir.file("1").createDir()
+        eventReceivedLatch.await()
+
+        testDir.file("1/2/3/4/5/6/7/8/9/10").createDir()
+        filesAddedLatch.countDown()
+
+        then:
+        totalLatch.await()
+    }
+
+    def "watcher doesn't add directories that have been deleted after change detection"() {
+        when:
+        def eventReceivedLatch = new CountDownLatch(1)
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset, onError) { watcher, event ->
+            eventReceivedLatch.countDown()
+            event.file.delete()
+        }
+
+        testDir.file("testdir").createDir()
+        eventReceivedLatch.await()
+        sleep(500)
+
+        then:
+        noExceptionThrown()
+        thrownInWatchExecution == null
+    }
+
+
+    def "watcher will stop if listener throws and error is forwarded"() {
+        when:
+        def onErrorStatus = this.<Pair<Boolean, Throwable>> blockingVar()
+        fileWatcher = fileWatcherFactory.watch(fileSystemSubset,
+            { onErrorStatus.set(Pair.of(fileWatcher.running, it)) },
+            { watcher, event -> throw new RuntimeException("!!") }
+        )
+
+        and:
+        testDir.file("new") << "new"
+
+        then:
+        await { assert !fileWatcher.running }
+        !onErrorStatus.get().left
+        onErrorStatus.get().right.message == "!!"
+    }
+
+    private void waitOn(CountDownLatch latch) {
+        //println "waiting..."
+        latch.await(waitForEventsMillis, TimeUnit.MILLISECONDS)
+    }
+
+    @ConditionBlock
+    private void await(Closure<?> closure) {
+        poll.within(waitForEventsMillis / 1000, closure)
+    }
+
+    private <T> BlockingVariable<T> blockingVar() {
+        new BlockingVariable<T>(waitForEventsMillis / 1000)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBackingTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBackingTest.groovy
new file mode 100644
index 0000000..b928f03
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/filewatch/jdk7/WatchServiceFileWatcherBackingTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.filewatch.jdk7
+
+import com.google.common.util.concurrent.ListeningExecutorService
+import com.google.common.util.concurrent.MoreExecutors
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileSystemSubset
+import org.gradle.internal.filewatch.FileWatcherListener
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import java.nio.file.WatchService
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+
+class WatchServiceFileWatcherBackingTest extends Specification {
+    @AutoCleanup("shutdown")
+    def executorService = Executors.newCachedThreadPool()
+
+    def "a stopped filewatcher shouldn't get started"() {
+        given:
+        def fileSystemSubset = FileSystemSubset.builder().build()
+        def onError = Mock(Action)
+        def listener = Mock(FileWatcherListener)
+        def watchService = Mock(WatchService)
+        def fileWatcher = new WatchServiceFileWatcherBacking(fileSystemSubset, onError, listener, watchService)
+        def listenerExecutorService = MoreExecutors.listeningDecorator(executorService)
+        def mockExecutorService = Mock(ListeningExecutorService)
+        def submitLatch = new CountDownLatch(1)
+
+        when:
+        def fileWatch = fileWatcher.start(mockExecutorService)
+        fileWatch.stop()
+        submitLatch.countDown()
+        sleep(1000)
+
+        then:
+        mockExecutorService.submit(_) >> { Runnable r ->
+            listenerExecutorService.submit(new Runnable() {
+                @Override
+                void run() {
+                    submitLatch.await()
+                    r.run()
+                }
+            })
+        }
+        0 * watchService.take()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutorTest.groovy
new file mode 100644
index 0000000..495ac62
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/DefaultBuildOperationExecutorTest.groovy
@@ -0,0 +1,214 @@
+/*
+ * 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.progress
+
+import org.gradle.internal.Factory
+import org.gradle.internal.TimeProvider
+import spock.lang.Specification
+
+class DefaultBuildOperationExecutorTest extends Specification {
+    def listener = Mock(InternalBuildListener)
+    def timeProvider = Mock(TimeProvider)
+    def executor = new DefaultBuildOperationExecutor(listener, timeProvider)
+
+    def "fires events when operation starts and finishes successfully"() {
+        def action = Mock(Factory)
+
+        when:
+        def result = executor.run("id", BuildOperationType.CONFIGURING_BUILD, action)
+
+        then:
+        result == "result"
+
+        and:
+        1 * timeProvider.currentTime >> 123L
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id"
+            assert operation.parentId == null
+            assert operation.operationType == BuildOperationType.CONFIGURING_BUILD
+            assert start.startTime == 123L
+        }
+        1 * action.create() >> "result"
+        1 * timeProvider.currentTime >> 124L
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id"
+            assert opResult.startTime == 123L
+            assert opResult.endTime == 124L
+            assert opResult.failure == null
+        }
+    }
+
+    def "fires events when operation starts and fails"() {
+        def action = Mock(Factory)
+        def failure = new RuntimeException()
+
+        when:
+        executor.run("id", BuildOperationType.CONFIGURING_BUILD, action)
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure
+
+        and:
+        1 * timeProvider.currentTime >> 123L
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id"
+            assert operation.parentId == null
+            assert operation.operationType == BuildOperationType.CONFIGURING_BUILD
+            assert start.startTime == 123L
+        }
+        1 * action.create() >> { throw failure }
+        1 * timeProvider.currentTime >> 124L
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id"
+            assert opResult.startTime == 123L
+            assert opResult.endTime == 124L
+            assert opResult.failure == failure
+        }
+    }
+
+    def "can query operation id from inside operation"() {
+        def action1 = Mock(Runnable)
+        def action2 = Mock(Runnable)
+
+        when:
+        executor.run("id", BuildOperationType.CONFIGURING_BUILD, action1)
+
+        then:
+        1 * action1.run() >> {
+            assert executor.currentOperationId == "id"
+            executor.run("id2", BuildOperationType.EXECUTING_TASKS, action2)
+        }
+        1 * action2.run() >> {
+            assert executor.currentOperationId == "id2"
+        }
+    }
+
+    def "cannot query operation id when no operation running"() {
+        when:
+        executor.currentOperationId
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "No operation is currently running."
+    }
+
+    def "attaches parent id when operation is nested inside another"() {
+        def action1 = Mock(Factory)
+        def action2 = Mock(Factory)
+        def action3 = Mock(Factory)
+
+        when:
+        def result = executor.run("id", BuildOperationType.CONFIGURING_BUILD, action1)
+
+        then:
+        result == "result"
+
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id"
+            assert operation.parentId == null
+        }
+        1 * action1.create() >> {
+            return executor.run("id2", BuildOperationType.EVALUATING_INIT_SCRIPTS, action2)
+        }
+
+        and:
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id2"
+            assert operation.parentId == "id"
+        }
+        1 * action2.create() >> {
+            return executor.run("id3", BuildOperationType.EXECUTING_TASKS, action3)
+        }
+
+        and:
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id3"
+            assert operation.parentId == "id2"
+        }
+        1 * action3.create() >> {
+            return "result"
+        }
+
+        and:
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id3"
+        }
+
+        and:
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id2"
+        }
+
+        and:
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id"
+        }
+    }
+
+    def "attaches parent id when sibling operation fails"() {
+        def action1 = Mock(Factory)
+        def action2 = Mock(Factory)
+        def action3 = Mock(Factory)
+
+        when:
+        def result = executor.run("id", BuildOperationType.CONFIGURING_BUILD, action1)
+
+        then:
+        result == "result"
+
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id"
+            assert operation.parentId == null
+        }
+        1 * action1.create() >> {
+            try {
+                executor.run("id2", BuildOperationType.EVALUATING_INIT_SCRIPTS, action2)
+            } catch (RuntimeException) {
+                // Ignore
+            }
+            return executor.run("id3", BuildOperationType.EXECUTING_TASKS, action3)
+        }
+
+        and:
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id2"
+            assert operation.parentId == "id"
+        }
+        1 * action2.create() >> { throw new RuntimeException() }
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id2"
+        }
+
+        and:
+        1 * listener.started(_, _) >> { BuildOperationInternal operation, OperationStartEvent start ->
+            assert operation.id == "id3"
+            assert operation.parentId == "id"
+        }
+        1 * action3.create() >> {
+            return "result"
+        }
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id3"
+        }
+
+        and:
+        1 * listener.finished(_, _) >> { BuildOperationInternal operation, OperationResult opResult ->
+            assert operation.id == "id"
+        }
+    }
+}
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
index ccb3a01..1f103f9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyKeeperTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyKeeperTest.groovy
@@ -50,8 +50,8 @@ class OperationsHierarchyKeeperTest extends ConcurrentSpecification {
     }
 
     def "may feed the parent logger"() {
-        def parent1 = Stub(ProgressLogger) { currentOperationId() >> 1 }
-        def parent2 = Mock(ProgressLogger) { currentOperationId() >> 2 }
+        def parent1 = Stub(ProgressLogger) { currentOperationId() >> new OperationIdentifier(1, 1) }
+        def parent2 = Mock(ProgressLogger) { currentOperationId() >> new OperationIdentifier(2, 1) }
 
         when:
         def h1 = manager.currentHierarchy(parent1)
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
index 0b11636..b7b8188 100644
--- a/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/internal/progress/OperationsHierarchyTest.groovy
@@ -68,7 +68,7 @@ class OperationsHierarchyTest extends Specification {
 
         then:
         hierarchy.currentOperationId() //can be called many times
-        id1.id == hierarchy.currentOperationId()
+        id1 == hierarchy.currentOperationId()
         id1.id == 3
     }
 
@@ -78,8 +78,9 @@ class OperationsHierarchyTest extends Specification {
         when: hierarchy.start()
 
         then:
-        hierarchy.currentOperationId() == 13
-        hierarchy.completeCurrentOperation() == 13
+        def currentId = hierarchy.currentOperationId()
+        currentId.id == 13
+        hierarchy.completeCurrentOperation() == currentId
 
         when: hierarchy.currentOperationId()
 
@@ -97,7 +98,7 @@ class OperationsHierarchyTest extends Specification {
         then:
         id1.id == 13
         id1.parentId == 2
-        removed == 13
+        removed.id == 13
         id2.id == 14
         id2.parentId == 2
     }
@@ -108,13 +109,14 @@ class OperationsHierarchyTest extends Specification {
 
         when: hierarchy.start()
 
-        then: hierarchy.currentOperationId() == 13
+        then: hierarchy.currentOperationId().id == 13
 
         when: ids << 100L //some child operation is added
 
         then:
-        hierarchy.currentOperationId() == 13 //current id is the same
-        hierarchy.completeCurrentOperation() == 13
+        def currentId = hierarchy.currentOperationId()
+        currentId.id == 13 //current id is the same
+        hierarchy.completeCurrentOperation() == currentId
 
         when:
         def id = hierarchy.start()
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
index 885e726..b06d5ad 100644
--- 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
@@ -26,10 +26,14 @@ import org.gradle.cache.CacheRepository
 import org.gradle.execution.*
 import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter
 import org.gradle.initialization.BuildCancellationToken
+import org.gradle.internal.TimeProvider
 import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.internal.environment.GradleBuildEnvironment
-import org.gradle.internal.service.ServiceRegistry
+import org.gradle.internal.event.DefaultListenerManager
 import org.gradle.internal.event.ListenerManager
+import org.gradle.internal.progress.BuildOperationExecutor
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.model.internal.inspect.ModelRuleSourceDetector
 import spock.lang.Specification
 
@@ -38,7 +42,6 @@ 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()
@@ -49,7 +52,7 @@ public class GradleScopeServicesTest extends Specification {
         parent.get(StartParameter) >> Stub(StartParameter)
         parent.get(GradleBuildEnvironment) >> Stub(GradleBuildEnvironment)
         parent.get(InMemoryTaskArtifactCache) >> Stub(InMemoryTaskArtifactCache)
-        parent.get(ListenerManager) >> listenerManager
+        parent.get(ListenerManager) >> new DefaultListenerManager()
         parent.get(CacheRepository) >> cacheRepository
         parent.get(PluginRegistry) >> pluginRegistryParent
         parent.get(DependencyManagementServices) >> Stub(DependencyManagementServices)
@@ -57,6 +60,9 @@ public class GradleScopeServicesTest extends Specification {
         parent.get(BuildCancellationToken) >> Stub(BuildCancellationToken)
         parent.get(ProjectConfigurer) >> Stub(ProjectConfigurer)
         parent.get(ModelRuleSourceDetector) >> Stub(ModelRuleSourceDetector)
+        parent.get(TimeProvider) >> Stub(TimeProvider)
+        parent.get(BuildOperationExecutor) >> Stub(BuildOperationExecutor)
+        parent.get(Instantiator) >> Stub(Instantiator)
         gradle.getStartParameter() >> startParameter
         pluginRegistryParent.createChild(_, _, _) >> pluginRegistryChild
     }
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
index 2d79615..b737f38 100644
--- 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
@@ -25,12 +25,12 @@ import org.gradle.cache.CacheRepository
 import org.gradle.cache.PersistentCache
 import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.internal.environment.GradleBuildEnvironment
+import org.gradle.internal.event.ListenerManager
 import org.gradle.internal.operations.BuildOperationProcessor
 import org.gradle.internal.operations.DefaultBuildOperationProcessor
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.DefaultServiceRegistry
 import org.gradle.internal.service.ServiceRegistry
-import org.gradle.internal.event.ListenerManager
 import spock.lang.Specification
 
 class TaskExecutionServicesTest extends Specification {
@@ -49,6 +49,7 @@ class TaskExecutionServicesTest extends Specification {
         _ * parent.get(CacheRepository) >> cacheRepository
         _ * parent.get(Instantiator) >> Mock(Instantiator)
         _ * parent.get(InMemoryTaskArtifactCache) >> Mock(InMemoryTaskArtifactCache)
+        _ * parent.get(StartParameter) >> Mock(StartParameter)
         _ * cacheRepository.cache(gradle, 'taskArtifacts') >> cacheBuilder
         _ * cacheBuilder.withDisplayName(!null) >> cacheBuilder
         _ * cacheBuilder.withLockOptions(!null) >> cacheBuilder
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutputTest.groovy
new file mode 100644
index 0000000..0e94893
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutputTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.logging.internal
+
+import org.gradle.internal.SystemProperties
+import org.gradle.logging.StyledTextOutput
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+class LinePrefixingStyledTextOutputTest extends Specification {
+
+    StringBuilder result
+
+    StyledTextOutput styledTextOutput
+
+    def setup() {
+        result = new StringBuilder();
+        styledTextOutput = output()
+    }
+
+    def "adds prefix to every line"() {
+        LinePrefixingStyledTextOutput output = new LinePrefixingStyledTextOutput(styledTextOutput, "[PREFIX]")
+
+        when:
+        output.println("1st line")
+        output.text("2nd line")
+        output.text(" - still 2nd line")
+        output.println()
+        output.text("3rd line")
+
+        then:
+        result.toString() == TextUtil.toPlatformLineSeparators("""[PREFIX]1st line
+[PREFIX]2nd line - still 2nd line
+[PREFIX]3rd line""")
+    }
+
+    def "allows not prefixing first line"() {
+        LinePrefixingStyledTextOutput output = new LinePrefixingStyledTextOutput(styledTextOutput, "[PREFIX]", false)
+
+        when:
+        output.println("1st line")
+        output.text("2nd line")
+        output.text(" - still 2nd line")
+        output.println()
+        output.text("3rd line")
+
+        then:
+        result.toString() == TextUtil.toPlatformLineSeparators("""1st line
+[PREFIX]2nd line - still 2nd line
+[PREFIX]3rd line""")
+    }
+
+
+    StyledTextOutput output() {
+        return new StyledTextOutput() {
+            @Override
+            StyledTextOutput append(char c) {
+                result.append(c)
+                return this
+            }
+
+            @Override
+            StyledTextOutput append(CharSequence csq) {
+                result.append(csq)
+                return this
+            }
+
+            @Override
+            StyledTextOutput append(CharSequence csq, int start, int end) {
+                result.append(csq, start, end)
+                return null
+            }
+
+            @Override
+            StyledTextOutput style(StyledTextOutput.Style style) {
+                return null
+            }
+
+            @Override
+            StyledTextOutput withStyle(StyledTextOutput.Style style) {
+                return null
+            }
+
+            @Override
+            StyledTextOutput text(Object text) {
+                result.append(text.toString());
+                return this
+            }
+
+            @Override
+            StyledTextOutput println(Object text) {
+                result.append(text.toString()).append(SystemProperties.instance.lineSeparator)
+                return this
+            }
+
+            @Override
+            StyledTextOutput format(String pattern, Object... args) {
+                return null
+            }
+
+            @Override
+            StyledTextOutput formatln(String pattern, Object... args) {
+                return null
+            }
+
+            @Override
+            StyledTextOutput println() {
+                result.append(SystemProperties.instance.lineSeparator)
+                return this
+            }
+
+            @Override
+            StyledTextOutput exception(Throwable throwable) {
+                return null
+            }
+        }
+
+    }
+
+
+}
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 7e01f7b..5f94e35 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
@@ -16,14 +16,15 @@
 package org.gradle.logging.internal
 
 import org.gradle.api.logging.LogLevel
+import org.gradle.internal.progress.OperationIdentifier
 import org.gradle.util.TextUtil
 import spock.lang.Specification
 
 import java.text.SimpleDateFormat
 
-class OutputSpecification extends Specification {
+abstract class OutputSpecification extends Specification {
 
-    private Long counter
+    private Long counter = 1
 
     protected String toNative(String value) {
         return TextUtil.toPlatformLineSeparators(value)
@@ -67,15 +68,19 @@ class OutputSpecification extends Specification {
 
     ProgressStartEvent start(Map args) {
         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)
+        long id = ++counter
+        return new ProgressStartEvent(new OperationIdentifier(id, parent), tenAm, 'category', args.description, args.shortDescription, args.loggingHeader, args.status)
     }
 
     ProgressEvent progress(String status) {
-        return new ProgressEvent(counter? counter:1, tenAm, 'category', status)
+        Long parent = counter - 1
+        long id = counter
+        return new ProgressEvent(new OperationIdentifier(id, parent), tenAm, 'category', status)
     }
 
     ProgressCompleteEvent complete(String status) {
-        return new ProgressCompleteEvent(counter? counter--:1, tenAm, 'category', 'description', status)
+        Long parent = counter - 1
+        long id = counter--
+        return new ProgressCompleteEvent(new OperationIdentifier(id, parent), tenAm, 'category', 'description', status)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ProgressLogEventGeneratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ProgressLogEventGeneratorTest.groovy
index 593ebb7..b9cc058 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ProgressLogEventGeneratorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ProgressLogEventGeneratorTest.groovy
@@ -441,4 +441,38 @@ class ProgressLogEventGeneratorTest extends OutputSpecification {
     def startWithHeader(String header) {
         return start(loggingHeader: header, shortDescription: header)
     }
+
+    def handlesMultipleOperationsInProgressAtOnce() {
+        ProgressLogEventGenerator generator = new ProgressLogEventGenerator(target, true)
+        given:
+        def firstStart = startWithHeader("task1")
+        def secondStart = startWithHeader("task2")
+        def secondProgress = progress("task2-progress")
+        def secondComplete = complete("task2-done")
+        def firstComplete = complete("task1-done")
+        when:
+        generator.onOutput(firstStart)
+        generator.onOutput(secondStart)
+        generator.onOutput(firstComplete)
+        generator.onOutput(secondProgress)
+        generator.onOutput(secondComplete)
+
+        then:
+        1 * target.onOutput({ StyledTextOutputEvent event ->
+            event.spans.size() == 1 && event.spans[0].text == toNative('task2\n')
+        })
+        1 * target.onOutput({ StyledTextOutputEvent event ->
+            event.spans.size() == 3 &&
+                event.spans[0].text == toNative('task1 ') &&
+                event.spans[1].text == toNative('task1-done') &&
+                event.spans[2].text == toNative('\n')
+        })
+        1 * target.onOutput({ StyledTextOutputEvent event ->
+            event.spans.size() == 3 &&
+                event.spans[0].text == toNative('task2 ') &&
+                event.spans[1].text == toNative('task2-done') &&
+                event.spans[2].text == toNative('\n')
+        })
+        0 * target._
+    }
 }
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 5af1ac2..c8e705c 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
@@ -16,11 +16,13 @@
 
 package org.gradle.process.internal
 
+import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.internal.jvm.Jvm
 import org.gradle.process.ExecResult
 import org.gradle.process.internal.streams.StreamsHandler
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.UsesNativeServices
 import org.junit.Rule
 import spock.lang.Ignore
@@ -31,6 +33,7 @@ import java.util.concurrent.Callable
 
 @UsesNativeServices
 @Timeout(60)
+ at LeaksFileHandles
 class DefaultExecHandleSpec extends Specification {
     @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
@@ -255,7 +258,7 @@ class DefaultExecHandleSpec extends Specification {
 
         then:
         result.rethrowFailure()
-        1 * streamsHandler.connectStreams(_ as Process, "foo proc")
+        1 * streamsHandler.connectStreams(_ as Process, "foo proc", _ as ExecutorFactory)
         1 * streamsHandler.start()
         1 * streamsHandler.stop()
         0 * streamsHandler._
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 611d890..9cf6149 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
@@ -22,6 +22,7 @@ import org.gradle.messaging.remote.ConnectionAcceptor
 import org.gradle.messaging.remote.ObjectConnection
 import org.gradle.process.ExecResult
 import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.MultithreadedTestCase
 import org.jmock.Mockery
 import org.jmock.integration.junit4.JMock
@@ -35,6 +36,7 @@ import static org.junit.Assert.assertThat
 import static org.junit.Assert.fail
 
 @RunWith(JMock.class)
+ at LeaksFileHandles
 class DefaultWorkerProcessTest extends MultithreadedTestCase {
     private final Mockery context = new JUnit4GroovyMockery()
     private final ExecHandle execHandle = context.mock(ExecHandle.class)
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 31e9201..86ab9d7 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
@@ -24,6 +24,7 @@ import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.DependencyInjectingInstantiator;
+import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.AbstractProject;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -31,6 +32,7 @@ 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.internal.tasks.execution.DefaultTaskExecutionContext;
 import org.gradle.api.specs.Spec;
 import org.gradle.internal.Actions;
 import org.gradle.internal.reflect.Instantiator;
@@ -83,6 +85,11 @@ public abstract class AbstractTaskTest {
         return type.cast(task);
     }
 
+    public void execute(TaskInternal task) {
+        project.getServices().get(TaskExecuter.class).execute(task, task.getState(), new DefaultTaskExecutionContext());
+        task.getState().rethrowFailure();
+    }
+
     @Before
     public final void setupRegistry() {
         serviceRegistry.add(Instantiator.class, instantiator);
diff --git a/subprojects/dependency-management/dependency-management.gradle b/subprojects/dependency-management/dependency-management.gradle
index d3e50de..7b07e34 100644
--- a/subprojects/dependency-management/dependency-management.gradle
+++ b/subprojects/dependency-management/dependency-management.gradle
@@ -1,7 +1,5 @@
 apply plugin: "groovy"
 
-import org.gradle.build.JarJar
-
 configurations {
     mvn3Input
 }
@@ -22,11 +20,7 @@ dependencies {
     runtime libraries.xbean //maven3 classes dependency
     runtime libraries.bouncycastle_provider
 
-    compile fileTree("$buildDir/libs/jarjar") {
-        builtBy 'jarJarMaven3'
-    }
-
-    mvn3Input libraries.maven3
+    compile libraries.maven3
 
     testCompile libraries.groovy
 
@@ -41,48 +35,9 @@ dependencies {
     testFixturesCompile project(":internalIntegTesting")
 }
 
-task jarJarMaven3(type: JarJar) {
-    inputJars = configurations.mvn3Input
-    outputDir = file("$buildDir/libs/jarjar")
-
-    //unfortunately, all those need to be jarjarred.
-    // Even if some library (like aether) is not included in maven-ant-tasks it has
-    // META-INF/plexus/components.xml that to jarjarred components.
-    rule('org.apache.maven.**', 'org.gradle.mvn3.org.apache.maven. at 1')
-    rule('org.codehaus.**', 'org.gradle.mvn3.org.codehaus. at 1')
-    rule('org.sonatype.**', 'org.gradle.mvn3.org.sonatype. at 1')
-
-    avoidConflictingPlexusComponents(it)
-}
-
 if (isWindows && javaVersion.java5) {
     compileTestGroovy.options.fork(memoryMaximumSize: '512m')
 }
 
-classpathManifest.dependsOn jarJarMaven3 //see GRADLE-2521
-
-//adding explicit task dependencies due to https://issues.gradle.org/browse/GRADLE-2481
-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).
-    //The implementation is the same but the 'hint' is different and this makes plexus fail to start.
-    //I'm removing the components.xml file from the sec-dispatcher jar.
-    //This file contains only single component so I think we can remove it.
-    task.doLast {
-        def plexusSec = "$outputDir/jarjar-plexus-sec-dispatcher-1.3.jar"
-        def plexusSecNoComps = "$plexusSec-noComps"
-        ant {
-            zip(destfile: plexusSecNoComps, update: true) {
-                zipfileset(src: plexusSec) {
-                    exclude(name: 'META-INF/plexus/components.xml')
-                }
-            }
-            move(file: plexusSecNoComps, tofile: plexusSec)
-        }
-    }
-}
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
index 76ba9ac..325939d 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -16,15 +16,18 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 import spock.lang.Issue
 
 import static org.hamcrest.Matchers.containsString
 
+ at RunWith(FluidDependenciesResolveRunner)
 class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
     @Rule
     public final TestResources testResources = new TestResources(testDirectoryProvider)
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ComponentReplacementIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ComponentReplacementIntegrationTest.groovy
index c3d9a97..08ffa37 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ComponentReplacementIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ComponentReplacementIntegrationTest.groovy
@@ -234,8 +234,8 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'a', 'b'
         declaredReplacements 'a->b'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'b') {
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'b') {
                     dep.useTarget 'org:d:1'
                 }
             }}
@@ -247,8 +247,8 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'a', 'b'
         declaredReplacements 'a->b'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'a') {
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'a') {
                     dep.useTarget 'org:b:1'
                 }
             }}
@@ -261,8 +261,8 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'a', 'b'
         declaredReplacements 'a->d'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'b') {
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'b') {
                     dep.useTarget 'org:d:1'
                 }
             }}
@@ -275,9 +275,9 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'c', 'd'
         declaredReplacements 'a->b'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'c') { dep.useTarget 'org:a:1' }
-                if (dep.requested.module == 'd') { dep.useTarget 'org:b:1' }
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'c') { dep.useTarget 'org:a:1' }
+                if (dep.requested.name == 'd') { dep.useTarget 'org:b:1' }
             }}
         """
         expect: resolvedModules 'b'
@@ -288,8 +288,8 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'a', 'b', 'c'
         declaredReplacements 'a->b', 'c->d'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'b') { dep.useTarget 'org:d:1' }
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'b') { dep.useTarget 'org:d:1' }
             }}
         """
         expect: resolvedModules 'a', 'd'
@@ -300,8 +300,8 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec {
         declaredDependencies 'a', 'b'
         declaredReplacements 'a->b', 'd->a'
         buildFile << """
-            configurations.all { resolutionStrategy.dependencySubstitution.eachModule { dep ->
-                if (dep.requested.module == 'b') { dep.useTarget 'org:d:1' }
+            configurations.all { resolutionStrategy.eachDependency { dep ->
+                if (dep.requested.name == 'b') { dep.useTarget 'org:d:1' }
             }}
         """
         //a->b->d->a
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ConfigurationDefaultsIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ConfigurationDefaultsIntegrationTest.groovy
new file mode 100644
index 0000000..a0a76fb
--- /dev/null
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ConfigurationDefaultsIntegrationTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+public class ConfigurationDefaultsIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def setup() {
+        mavenRepo.module("org", "foo").publish()
+        mavenRepo.module("org", "bar").publish()
+
+        buildFile << """
+configurations {
+    conf
+    child.extendsFrom conf
+}
+repositories {
+    maven { url '${mavenRepo.uri}' }
+}
+
+if (project.hasProperty('explicitDeps')) {
+    dependencies {
+        conf "org:bar:1.0"
+    }
+}
+task checkDefault << {
+    if (project.hasProperty('resolveChild')) {
+        configurations.child.resolve()
+    }
+
+    def deps = configurations.conf.incoming.resolutionResult.allDependencies
+    assert deps*.selected.id.displayName == ['org:foo:1.0']
+
+    def files = configurations.conf.files
+    assert files*.name == ["foo-1.0.jar"]
+}
+task checkExplicit << {
+    def deps = configurations.conf.incoming.resolutionResult.allDependencies
+    assert deps*.selected.id.displayName == ['org:bar:1.0']
+
+    def files = configurations.conf.files
+    assert files*.name == ["bar-1.0.jar"]
+}
+"""
+    }
+
+    def "can use defaultDependencies to specify default dependencies"() {
+        buildFile << """
+def fooDep = project.dependencies.create("org:foo:1.0")
+configurations.conf.defaultDependencies { deps ->
+    deps.add(fooDep)
+}
+"""
+
+        expect:
+        succeeds "checkDefault"
+
+        when:
+        executer.withArgument("-PresolveChild")
+
+        then:
+        succeeds "checkDefault"
+
+        when:
+        executer.withArgument("-PexplicitDeps")
+
+        then:
+        succeeds "checkExplicit"
+    }
+
+    def "can use beforeResolve to specify default dependencies"() {
+        buildFile << """
+def fooDep = project.dependencies.create("org:foo:1.0")
+configurations.conf.incoming.beforeResolve {
+    if (configurations.conf.dependencies.empty) {
+        configurations.conf.dependencies.add(fooDep)
+    }
+}
+"""
+
+        expect:
+        succeeds "checkDefault"
+
+        when:
+        executer.withArgument("-PexplicitDeps")
+
+        then:
+        succeeds "checkExplicit"
+    }
+
+    def "deprecation warning informs about defaultDependencies if beforeResolve used to add dependencies to observed configuration"() {
+        buildFile << """
+def fooDep = project.dependencies.create("org:foo:1.0")
+configurations.conf.incoming.beforeResolve {
+    if (configurations.conf.dependencies.empty) {
+        configurations.conf.dependencies.add(fooDep)
+    }
+}
+"""
+
+
+        when:
+        executer.withArgument("-PresolveChild")
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds "checkDefault"
+
+        and:
+        output.contains "Changed dependencies of configuration ':conf' after it has been included in dependency resolution."
+        output.contains "Use 'defaultDependencies' instead of 'beforeResolve' to specify default dependencies for a configuration."
+        output.contains "Changed dependencies of parent of configuration ':child' after it has been resolved."
+    }
+}
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyExcludeResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyExcludeResolveIntegrationTest.groovy
index 0bebf8e..d9526d0 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyExcludeResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyExcludeResolveIntegrationTest.groovy
@@ -17,8 +17,11 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 import spock.lang.Unroll
 
+ at RunWith(FluidDependenciesResolveRunner)
 class DependencyExcludeResolveIntegrationTest extends AbstractDependencyResolutionTest {
     /**
      * Dependency exclude rules defined through Gradle DSL.
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy
index bba7f39..37d6c63 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy
@@ -16,8 +16,10 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.*
+import org.junit.runner.RunWith
 import spock.lang.Issue
 
+ at RunWith(FluidDependenciesResolveRunner)
 class DependencyResolutionEventsIntegrationTest extends AbstractIntegrationSpec {
 
     @Issue("https://issues.gradle.org/browse/GRADLE-2047")
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
index 3f6bfdb..241308f 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
@@ -18,9 +18,12 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
+ at RunWith(FluidDependenciesResolveRunner)
 class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 
     /**
@@ -65,6 +68,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
         succeeds("check")
     }
 
+
     void "forces multiple modules by rule"()
     {
         mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
@@ -365,6 +369,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
         noExceptionThrown()
     }
 
+
     void "can blacklist a version"()
     {
         mavenRepo.module("org.utils", "a",  '1.4').publish()
@@ -837,4 +842,4 @@ conf
         gradle.startParameter.taskNames += 'resolveConf'
         """
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
index 350b51e..4bd311c 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy
@@ -16,9 +16,8 @@
 
 
 package org.gradle.integtests.resolve
-
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import spock.lang.Ignore
+import org.gradle.util.TextUtil
 import spock.lang.Unroll
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
@@ -48,21 +47,20 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
-                        if (it.requested.group == 'org.utils' && it.requested.module != 'optional-lib') {
-                            it.useVersion '1.5'
+                    all {
+                        if (it.requested instanceof ModuleComponentSelector) {
+                            if (it.requested.group == 'org.utils' && it.requested.module != 'optional-lib') {
+                                it.useTarget group: 'org.utils', name: it.requested.module, version: '1.5'
+                            }
                         }
                     }
                 }
-	            failOnVersionConflict()
-	        }
+                failOnVersionConflict()
+            }
 """
 
-        when:
-        run("resolveConf")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "resolveConf"
     }
 
     void "module forced by rule has correct selection reason"()
@@ -84,56 +82,25 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
+                    all {
                         if (it.requested.group == 'org.utils') {
-                            it.useVersion '1.5'
+                            it.useTarget group: 'org.utils', name: it.requested.module, version: '1.5'
                         }
                     }
                 }
-	        }
-
-	        task check << {
-	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
-	            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]
-	        }
-"""
-
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
-    }
-
-    @Ignore("Deprecation not yet added")
-    void "warns about using deprecated resolution rules"()
-    {
-        mavenRepo.module("org.utils", "api", '1.5').publish()
-
-        buildFile << """
-            $common
-
-            dependencies {
-                conf 'org.utils:api:1.3'
             }
 
-            configurations.conf.resolutionStrategy {
-                eachDependency {
-                    it.useVersion "1.5"
-                }
-	        }
+            task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies
+                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]
+            }
 """
 
-        executer.withDeprecationChecksDisabled()
-
-        when:
-        succeeds()
-
-        then:
-        output.contains("The ResolutionStrategy.eachDependency() method has been deprecated and is scheduled to be removed in Gradle 3.0. Please use the DependencySubstitution.eachModule() method instead.")
+        expect:
+        succeeds "check"
     }
 
     void "all rules are executed in order and last one wins"()
@@ -153,42 +120,40 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
+                    all {
                         assert it.target == it.requested
-                        it.useVersion '1.4'
+                        it.useTarget group: it.requested.group, name: it.requested.module, version: '1.4'
                     }
-                    eachModule {
+                    all {
                         assert it.target.version == '1.4'
                         assert it.target.module == it.requested.module
                         assert it.target.group == it.requested.group
-                        it.useVersion '1.5'
+                        it.useTarget group: it.requested.group, name: it.requested.module, version: '1.5'
                     }
-                    eachModule {
+                    all {
                         assert it.target.version == '1.5'
                         //don't change the version
                     }
                 }
-	        }
+            }
 
-	        task check << {
-	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+            task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies
                 assert deps.size() == 2
-                deps.each {
-	                assert it.selected.id.version == '1.5'
-	                assert it.selected.selectionReason.selectedByRule
-	                assert it.selected.selectionReason.description == 'selected by rule'
-	            }
-	        }
-"""
 
-        when:
-        run("check")
+                def apiDep = deps.find({ it.selected.id.module == 'api' }).selected
+                assert apiDep.id.version == '1.5'
+                assert !apiDep.selectionReason.forced
+                assert apiDep.selectionReason.selectedByRule
+                assert apiDep.selectionReason.description == 'selected by rule'
+            }
+"""
 
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
-    void "all rules are executed in order and last one wins, including deprecated resolution rules"()
+    void "all rules are executed in order and last one wins, including resolution rules"()
     {
         mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
         mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
@@ -205,9 +170,9 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
+                    all {
                         assert it.target == it.requested
-                        it.useVersion '1.4'
+                        it.useTarget group: 'org.utils', name: it.requested.module, version: '1.4'
                     }
                 }
                 eachDependency {
@@ -217,77 +182,29 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     it.useVersion '1.5'
                 }
                 dependencySubstitution {
-                    eachModule {
+                    all {
                         assert it.target.version == '1.5'
                         //don't change the version
                     }
                 }
-	        }
-
-	        task check << {
-	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
-                assert deps.size() == 2
-                deps.each {
-	                assert it.selected.id.version == '1.5'
-	                assert it.selected.selectionReason.selectedByRule
-	                assert it.selected.selectionReason.description == 'selected by rule'
-	            }
-	        }
-"""
-        executer.withDeprecationChecksDisabled()
-
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
-    }
-
-    void "can unforce the version"()
-    {
-        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
-        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
-
-        mavenRepo.module("org.utils", "api", '1.3').publish()
-        mavenRepo.module("org.utils", "api", '1.5').publish()
-
-        buildFile << """
-            $common
-
-            dependencies {
-                conf 'org.utils:impl:1.3'
             }
 
-            configurations.conf.resolutionStrategy {
-                force("org.utils:impl:1.5", "org.utils:api:1.5")
-
-                dependencySubstitution {
-    	            eachModule {
-                        it.useVersion it.requested.version
-	                }
-                }
-	        }
-
-	        task check << {
-	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+            task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies
                 assert deps.size() == 2
                 deps.each {
-	                assert it.selected.id.version == '1.3'
-                    def reason = it.selected.selectionReason
-                    assert !reason.forced
-                    assert reason.selectedByRule
-	            }
-	        }
+                    assert it.selected.id.version == '1.5'
+                    assert it.selected.selectionReason.selectedByRule
+                    assert it.selected.selectionReason.description == 'selected by rule'
+                }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
-    void "rule are applied after forced modules"()
+    void "can unforce the version"()
     {
         mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
         mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
@@ -306,30 +223,26 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 force("org.utils:impl:1.5", "org.utils:api:1.5")
 
                 dependencySubstitution {
-                    eachModule {
-                        assert it.target.version == '1.5'
-                        it.useVersion '1.3'
+                    all {
+                        it.useTarget it.requested
                     }
                 }
-	        }
+            }
 
-	        task check << {
-	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+            task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies
                 assert deps.size() == 2
                 deps.each {
-	                assert it.selected.id.version == '1.3'
+                    assert it.selected.id.version == '1.3'
                     def reason = it.selected.selectionReason
                     assert !reason.forced
                     assert reason.selectedByRule
-	            }
-	        }
+                }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     void "forced modules and rules coexist"()
@@ -338,7 +251,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
         mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
 
         mavenRepo.module("org.utils", "api", '1.3').publish()
-        mavenRepo.module("org.utils", "api", '1.5').publish()
+        mavenRepo.module("org.utils", "api", '1.6').publish()
 
         buildFile << """
             $common
@@ -351,36 +264,27 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 force("org.utils:impl:1.5")
 
                 dependencySubstitution {
-                    withModule("org.utils:api") {
-                        assert it.target == it.requested
-                        it.useVersion '1.5'
-                    }
+                    substitute module("org.utils:api") with module("org.utils:api:1.6")
                 }
-	        }
+            }
 
-	        task check << {
+            task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies
-                assert deps.find {
-                    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.module == 'api' &&
-                    it.selected.id.version == '1.5' &&
-                    !it.selected.selectionReason.forced &&
-                    it.selected.selectionReason.selectedByRule
-	            }
-	        }
-"""
+                def apiDep = deps.find({ it.selected.id.module == 'api' }).selected
+                assert apiDep.id.version == '1.6'
+                assert !apiDep.selectionReason.forced
+                assert apiDep.selectionReason.selectedByRule
 
-        when:
-        run("check")
+                def implDep = deps.find({ it.selected.id.module == 'impl' }).selected
+                assert implDep.id.version == '1.5'
+                assert implDep.selectionReason.forced
+                assert !implDep.selectionReason.selectedByRule
+            }
+"""
 
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     void "rule selects a dynamic version"()
@@ -396,28 +300,25 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:api:1.3'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                it.useVersion '1.+'
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org.utils:api:1.3') with module('org.utils:api:1.+')
+            }
 
-	        task check << {
+            task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 1
                 assert deps[0].requested.version == '1.3'
                 assert deps[0].selected.id.version == '1.5'
                 assert !deps[0].selected.selectionReason.forced
                 assert deps[0].selected.selectionReason.selectedByRule
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
-    void "can replace external dependency with project dependency"()
+    void "can substitute modules with project dependency using #name"()
     {
         settingsFile << 'include "api", "impl"'
         buildFile << """
@@ -428,11 +329,13 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf group: "org.utils", name: "api", version: "1.5", configuration: "conf"
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule(group: "org.utils", name: "api") {
-                    it.useTarget project(":api")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("$selector") with project(":api")
                 }
 
-                task check << {
+                task check(dependsOn: configurations.conf) << {
+                    assert configurations.conf.collect { it.name } == ['api.jar']
+
                     def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                     assert deps.size() == 1
                     assert deps[0] instanceof org.gradle.api.artifacts.result.ResolvedDependencyResult
@@ -446,8 +349,16 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
             }
 """
 
-        expect:
-        succeeds("impl:check")
+        when:
+        succeeds "impl:check"
+
+        then:
+        executedAndNotSkipped ":api:jar"
+
+        where:
+        name                 | selector
+        "matching module"    | "org.utils:api"
+        "matching component" | "org.utils:api:1.5"
     }
 
     void "can access built artifacts from substituted project dependency"()
@@ -475,25 +386,27 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf group: "org.utils", name: "api", version: "1.5"
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule(group: "org.utils", name: "api") {
-                    it.useTarget project(":api")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api") with project(":api")
                 }
 
                 task check(dependsOn: configurations.conf) << {
                     def files = configurations.conf.files
-                    assert files*.name.sort() == ["artifact.txt"]
-                    assert files*.text.sort() == ["Lajos"]
+                    assert files*.name.sort() == ["api.jar", "artifact.txt"]
+                    assert files[1].text == "Lajos"
                 }
-                // TODO:PREZI Remove this when PR#412 is merged
-                check.dependsOn project(":api").build
             }
 """
 
-        expect:
-        succeeds("impl:check")
+        when:
+        succeeds ":impl:check"
+
+        then:
+        executedAndNotSkipped ":api:build"
     }
 
-    void "can replace project dependency with external dependency"()
+    @Unroll
+    void "can replace project dependency #projectGroup:api:#projectVersion with external dependency org.utils:api:1.5"()
     {
         mavenRepo.module("org.utils", "api", '1.5').publish()
 
@@ -501,17 +414,20 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
         buildFile << """
             $common
-
+            project(":api") {
+                group = "$projectGroup"
+                version = "$projectVersion"
+            }
             project(":impl") {
                 dependencies {
                     conf project(path: ":api", configuration: "default")
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withProject(":api") {
-                    it.useTarget group: "org.utils", name: "api", version: "1.5"
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute project(":api") with module("org.utils:api:1.5")
                 }
 
-                task check << {
+                task check(dependsOn: configurations.conf) << {
                     def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                     assert deps.size() == 1
                     assert deps[0] instanceof org.gradle.api.artifacts.result.ResolvedDependencyResult
@@ -525,8 +441,18 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
             }
 """
 
-        expect:
-        succeeds("impl:check")
+        when:
+        succeeds "impl:check"
+
+        then:
+        notExecuted ":api:jar"
+
+        where:
+        projectVersion | projectGroup | scenario
+        "1.5"          | "org.utils"  | "the same as the external dependency"
+        "2.0"          | "org.utils"  | "GAV different, version only"
+        "1.5"          | "my.org.utils"  | "GAV different, group only"
+        "2.0"          | "my.org.utils"  | "GAV different, version and group"
     }
 
     void "can replace transitive external dependency with project dependency"()
@@ -542,11 +468,13 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf group: "org.utils", name: "impl", version: "1.5"
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule("org.utils:api") {
-                    it.useTarget project(":api")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api") with project(":api")
                 }
 
-                task check << {
+                task check(dependsOn: configurations.conf) << {
+                    assert configurations.conf.collect { it.name } == ['impl-1.5.jar', 'api.jar']
+
                     def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                     assert deps.size() == 2
                     assert deps.find {
@@ -566,8 +494,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
             }
 """
 
-        expect:
-        succeeds("test:check")
+        when:
+        succeeds "test:check"
+
+        then:
+        executedAndNotSkipped ":api:jar"
     }
 
     void "can replace client module dependency with project dependency"()
@@ -582,8 +513,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf module(group: "org.utils", name: "api", version: "1.5")
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule("org.utils:api") {
-                    it.useTarget project(":api")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api") with project(":api")
                 }
 
                 task check << {
@@ -601,7 +532,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
     }
 
     void "can replace client module's transitive dependency with project dependency"()
@@ -619,8 +550,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     }
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule("org.utils:api") {
-                    it.useTarget project(":api")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api") with project(":api")
                 }
 
                 task check << {
@@ -644,7 +575,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
     }
 
     void "can replace external dependency declared in extended configuration with project dependency"()
@@ -665,8 +596,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf group: "org.utils", name: "api", version: "1.5"
                 }
 
-                configurations.testConf.resolutionStrategy.dependencySubstitution.withModule("org.utils:api") {
-                    it.useTarget project(":api")
+                configurations.testConf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api") with project(":api")
                 }
 
                 task checkConf << {
@@ -698,7 +629,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
     }
 
     void "can replace forced external dependency with project dependency"()
@@ -716,8 +647,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 configurations.conf.resolutionStrategy {
                     force("org.utils:api:1.3")
 
-                    dependencySubstitution.withModule("org.utils:api") {
-                        it.useTarget project(":api")
+                    dependencySubstitution {
+                        substitute module("org.utils:api") with project(":api")
                     }
                 }
 
@@ -736,7 +667,44 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
+    }
+
+    void "get useful error message when replacing an external dependency with a project that does not exist"()
+    {
+        settingsFile << 'include "api", "impl"'
+
+        buildFile << """
+            $common
+
+            project(":impl") {
+                dependencies {
+                    conf group: "org.utils", name: "api", version: "1.5"
+                }
+
+                configurations.conf.resolutionStrategy {
+                    force("org.utils:api:1.3")
+
+                    dependencySubstitution {
+                        substitute module("org.utils:api") with project(":doesnotexist")
+                    }
+                }
+
+                task check << {
+                    def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
+                    assert deps.size() == 1
+                    assert deps[0] instanceof org.gradle.api.artifacts.result.UnresolvedDependencyResult
+                }
+            }
+"""
+
+        expect:
+        fails "impl:check"
+        errorOutput.contains(TextUtil.toPlatformLineSeparators("""
+Execution failed for task ':impl:resolveConf'.
+> Could not resolve all dependencies for configuration ':impl:conf'.
+   > project ':doesnotexist' not found.
+"""))
     }
 
     void "replacing external module dependency with project dependency keeps the original configuration"()
@@ -766,19 +734,22 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     compile group: "org.utils", name: "api", version: "1.5", configuration: "conf"
                 }
 
-                configurations.compile.resolutionStrategy.dependencySubstitution.withModule("org.utils:api") {
-                    it.useTarget project(":api")
+                configurations.compile.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api:1.5") with project(":api")
                 }
 
-                task check << {
+                task check(dependsOn: configurations.compile) << {
                     def files = configurations.compile.files
-                    assert files*.name.sort() == ["conf.txt"]
+                    assert files*.name.sort() == ["api.jar", "conf.txt"]
                 }
             }
 """
 
-        expect:
-        succeeds("impl:check")
+        when:
+        succeeds "impl:check"
+
+        then:
+        executedAndNotSkipped ":api:jar"
     }
 
     void "replacing external module dependency with project dependency keeps the original transitivity"()
@@ -794,11 +765,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf (group: "org.utils", name: "impl", version: "1.5") { transitive = false }
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withModule("org.utils:impl") {
-                    it.useTarget project(":impl")
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:impl") with project(":impl")
                 }
 
-                task check << {
+                task check(dependsOn: configurations.conf) << {
                     def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                     assert deps.size() == 1
                     assert deps.find {
@@ -812,8 +783,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
             }
 """
 
-        expect:
-        succeeds("test:check")
+        when:
+        succeeds "test:check"
+
+        then:
+        notExecuted ":api:jar"
     }
 
     void "external dependency substituted for a project dependency participates in conflict resolution"()
@@ -831,8 +805,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf "org.utils:api:2.0"
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.withProject(":api") {
-                    it.useTarget "org.utils:api:1.6"
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute project(":api") with module("org.utils:api:1.6")
                 }
 
                 task check << {
@@ -863,7 +837,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
     }
 
     @Unroll
@@ -886,10 +860,8 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                     conf "org.utils:api:2.0"
                 }
 
-                configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                    if (it.requested.module == "api" && it.requested.version == "1.5") {
-                        it.useTarget project(":api")
-                    }
+                configurations.conf.resolutionStrategy.dependencySubstitution {
+                    substitute module("org.utils:api:1.5") with project(":api")
                 }
 
                 task check << {
@@ -916,7 +888,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 """
 
         expect:
-        succeeds("impl:check")
+        succeeds "impl:check"
 
         where:
         apiProjectVersion | winner                                | selectedByRule
@@ -938,14 +910,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:a:1.2', 'org.utils:b:1.3'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                // a:1.2 is blacklisted, 1.4 should be used instead:
-                if (it.requested.module == 'a' && it.requested.version == '1.2') {
-                    it.useVersion '1.4'
-                }
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org.utils:a:1.2') with module('org.utils:a:1.4')
+            }
 
-	        task check << {
+            task check << {
                 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'
@@ -953,14 +922,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 assert a.selectionReason.selectedByRule
                 assert !a.selectionReason.forced
                 assert a.selectionReason.description == 'selected by rule and conflict resolution'
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     void "can blacklist a version that is not used"()
@@ -976,28 +942,22 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:a:1.2', 'org.utils:b:1.3'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                // a:1.2 is blacklisted, 1.2.1 should be used instead:
-                if (it.requested.module == 'a' && it.requested.version == '1.2') {
-                    it.useVersion '1.2.1'
-                }
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org.utils:a:1.2') with module('org.utils:a:1.2.1')
+            }
 
-	        task check << {
+            task check << {
                 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
                 assert !a.selectionReason.forced
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     def "can use custom versioning scheme"()
@@ -1011,26 +971,23 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:api:default'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
+            configurations.conf.resolutionStrategy.dependencySubstitution.all {
                 if (it.requested.version == 'default') {
-                    it.useVersion '1.3'
+                    it.useTarget group: it.requested.group, name: it.requested.module, version: '1.3'
                 }
-	        }
+            }
 
-	        task check << {
+            task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 1
                 deps[0].requested.version == 'default'
                 deps[0].selected.id.version == '1.3'
                 deps[0].selected.selectionReason.selectedByRule
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     def "can use custom versioning scheme for transitive dependencies"()
@@ -1045,27 +1002,24 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:impl:1.3'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
+            configurations.conf.resolutionStrategy.dependencySubstitution.all {
                 if (it.requested.version == 'default') {
-                    it.useVersion '1.3'
+                    it.useTarget group: it.requested.group, name: it.requested.module, version: '1.3'
                 }
-	        }
+            }
 
-	        task check << {
+            task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 2
                 def api = deps.find { it.requested.module == 'api' }
                 api.requested.version == 'default'
                 api.selected.id.version == '1.3'
                 api.selected.selectionReason.selectedByRule
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     void "rule selects unavailable version"()
@@ -1079,11 +1033,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:api:1.3'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                it.useVersion '1.123.15' //does not exist
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org.utils:api:1.3') with module('org.utils:api:1.123.15')
+            }
 
-	        task check << {
+            task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 1
                 assert deps[0].attempted.group == 'org.utils'
@@ -1092,11 +1046,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 assert deps[0].attemptedReason.selectedByRule
                 assert deps[0].failure.message.contains('1.123.15')
                 assert deps[0].requested.version == '1.3'
-	        }
+            }
 """
 
         when:
-        def failure = runAndFail("check", "resolveConf")
+        fails "check", "resolveConf"
 
         then:
         failure.assertResolutionFailure(":conf")
@@ -1135,23 +1089,20 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
+                    all {
                         requested << "\$it.requested.module:\$it.requested.version"
                     }
                 }
-	        }
+            }
 
-	        task check << {
+            task check << {
                 configurations.conf.resolve()
                 assert requested == ['impl:1.3', 'foo:2.0', 'bar:2.0', 'api:1.3', 'api:1.5']
-	        }
+            }
 """
 
-        when:
-        run("check")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds "check"
     }
 
     void "runtime exception when evaluating rule yields decent exception"()
@@ -1171,18 +1122,18 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
 
             configurations.conf.resolutionStrategy {
                 dependencySubstitution {
-                    eachModule {
-                        it.useVersion '1.3' //happy
+                    all {
+                        it.useTarget group: it.requested.group, name: it.requested.module, version: '1.3' //happy
                     }
-                    eachModule {
+                    all {
                         throw new RuntimeException("Unhappy :(")
                     }
                 }
-	        }
+            }
 """
 
         when:
-        def failure = runAndFail("resolveConf")
+        fails "resolveConf"
 
         then:
         failure.assertResolutionFailure(":conf")
@@ -1191,6 +1142,78 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 .assertFailedDependencyRequiredBy(":root:1.0")
     }
 
+    void "reasonable error message when attempting to substitute with an unversioned module selector"() {
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            version = 1.0
+
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute project(":foo") with module("org.gradle:test")
+            }
+"""
+
+        when:
+        fails "dependencies"
+
+        then:
+        failure.assertHasCause("Must specify version for target of dependency substitution")
+    }
+
+    void "reasonable error message when attempting to create an invalid selector"() {
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            version = 1.0
+
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module(":foo:bar:baz:") with module("")
+            }
+"""
+
+        when:
+        fails "dependencies"
+
+        then:
+        failure.assertHasCause("Cannot convert the provided notation to an object of type ComponentSelector: :foo:bar:baz:")
+    }
+
+    void "reasonable error message when attempting to add rule that substitutes with an unversioned module selector"() {
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            version = 1.0
+
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                def moduleSelector = module("org.gradle:test")
+                all {
+                    it.useTarget moduleSelector
+                }
+            }
+"""
+
+        when:
+        fails "dependencies"
+
+        then:
+        failure.assertHasCause("Must specify version for target of dependency substitution")
+    }
+
     void "can substitute module name and resolve conflict"()
     {
         mavenRepo.module("org.utils", "a",  '1.2').publish()
@@ -1204,11 +1227,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org.utils:a:1.2', 'org.utils:b:2.0'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.withModule("org.utils:a") {
-                it.useTarget(it.requested.group + ':b:2.1')
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org.utils:a:1.2') with module('org.utils:b:2.1')
+            }
 
-	        task check << {
+            task check << {
                 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' }
@@ -1217,11 +1240,11 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 assert b.selectionReason.selectedByRule
                 assert !b.selectionReason.forced
                 assert b.selectionReason.description == 'selected by rule and conflict resolution'
-	        }
+            }
 """
 
         when:
-        run("check", "dependencies")
+        succeeds "check", "dependencies"
 
         then:
         output.contains(toPlatformLineSeparators("""conf
@@ -1245,15 +1268,15 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org:a:1.0', 'foo:b:1.0'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
+            configurations.conf.resolutionStrategy.dependencySubstitution.all {
                 if (it.requested.group == 'foo') {
                     it.useTarget('org:' + it.requested.module + ':' + it.requested.version)
                 }
-	        }
+            }
 """
 
         when:
-        run("dependencies")
+        succeeds "dependencies"
 
         then:
         output.contains(toPlatformLineSeparators("""
@@ -1279,15 +1302,13 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org:a:1.0', 'foo:bar:baz'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                if (it.requested.group == 'foo') {
-                    it.useTarget group: 'org', name: 'b', version: '1.0'
-                }
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('foo:bar:baz') with module('org:b:1.0')
+            }
 """
 
         when:
-        run("dependencies")
+        succeeds "dependencies"
 
         then:
         output.contains(toPlatformLineSeparators("""
@@ -1306,13 +1327,13 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org:a:1.0', 'foo:bar:baz'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
+            configurations.conf.resolutionStrategy.dependencySubstitution.all {
                 it.useTarget "foobar"
-	        }
+            }
 """
 
         when:
-        runAndFail("dependencies")
+        fails "dependencies"
 
         then:
         failure.assertResolutionFailure(":conf").assertHasCause("Invalid format: 'foobar'")
@@ -1331,15 +1352,13 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec
                 conf 'org:a:1.0', 'org:a:2.0'
             }
 
-            configurations.conf.resolutionStrategy.dependencySubstitution.eachModule {
-                if (it.requested.module == 'a' && it.requested.version == '1.0') {
-                    it.useTarget group: 'org', name: 'c', version: '1.1'
-                }
-	        }
+            configurations.conf.resolutionStrategy.dependencySubstitution {
+                substitute module('org:a:1.0') with module('org:c:1.1')
+            }
 """
 
         when:
-        run("dependencies")
+        succeeds "dependencies"
 
         then:
         output.contains(toPlatformLineSeparators("""
@@ -1351,42 +1370,6 @@ conf
 """))
     }
 
-    def "module selected by conflict resolution can be selected again in a another pass of conflict resolution"()
-    {
-        mavenRepo.module("org", "a", "1.0").publish()
-        mavenRepo.module("org", "a", "2.0").dependsOn("org", "b", "2.5").publish()
-        mavenRepo.module("org", "b", "3.0").publish()
-        mavenRepo.module("org", "b", "4.0").publish()
-
-        /*
-        I agree this dependency set is awkward but it is the simplest reproducible scenario
-        a:1.0
-        a:2.0 -> b:2.5
-        b:3.0
-        b:4.0
-
-        the conflict resolution of b:
-        1st pass: b:3 vs b:4(wins)
-        2nd pass: b:2.5 vs b:4(wins *again*)
-        */
-
-        buildFile << """
-            $common
-
-            dependencies {
-                conf 'org:b:3.0', 'org:b:4.0', 'org:a:1.0', 'org:a:2.0'
-            }
-
-            task check << {
-                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 }
-            }
-"""
-
-        expect:
-        run("check")
-    }
-
     String getCommon() {
         """
         allprojects {
@@ -1399,7 +1382,10 @@ conf
                 maven { url "${mavenRepo.uri}" }
             }
 
-            task resolveConf << { configurations.conf.files }
+            task jar(type: Jar) { baseName = project.name }
+            artifacts { conf jar }
+
+            task resolveConf(dependsOn: configurations.conf) << { configurations.conf.files }
         }
 
         //resolving the configuration at the end:
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy
index f3570f8..2a77b06 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DetachedConfigurationsIntegrationTest.groovy
@@ -17,8 +17,11 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 import spock.lang.Issue
 
+ at RunWith(FluidDependenciesResolveRunner)
 class DetachedConfigurationsIntegrationTest extends AbstractIntegrationSpec {
 
     @Issue("GRADLE-2889")
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy
index 9dd235e..7234b8f 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ExtendingConfigurationsIntegrationTest.groovy
@@ -16,8 +16,11 @@
 package org.gradle.integtests.resolve;
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 import spock.lang.Issue;
 
+ at RunWith(FluidDependenciesResolveRunner)
 public class ExtendingConfigurationsIntegrationTest extends AbstractDependencyResolutionTest {
 
     @Issue("GRADLE-2873")
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MetadataArtifactResolveTestFixture.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MetadataArtifactResolveTestFixture.groovy
index b1312ae..b1e511e 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MetadataArtifactResolveTestFixture.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/MetadataArtifactResolveTestFixture.groovy
@@ -183,10 +183,37 @@ task verify {
         def rootId = configurations.${config}.incoming.resolutionResult.root.id
         assert rootId instanceof ProjectComponentIdentifier
 
-        dependencies.createArtifactResolutionQuery()
+        def result = dependencies.createArtifactResolutionQuery()
             .forComponents(rootId)
             .withArtifacts($requestedComponent, $requestedArtifact)
             .execute()
+
+        assert result.components.size() == 1
+
+        // Check generic component result
+        def componentResult = result.components.iterator().next()
+        assert componentResult.id.displayName == 'project :'
+        assert componentResult instanceof $expectedComponentResult.name
+"""
+
+        if(expectedComponentResult == UnresolvedComponentResult) {
+            buildFile << createUnresolvedComponentResultVerificationCode()
+        }
+
+        buildFile << """
+        def expectedMetadataFileNames = ${expectedMetadataFiles.collect { "'" + it.name + "'" }} as Set
+
+        for(component in result.resolvedComponents) {
+            def resolvedArtifacts = component.getArtifacts($requestedArtifact).findAll { it instanceof ResolvedArtifactResult }
+            assert expectedMetadataFileNames.size() == resolvedArtifacts.size()
+
+            ${createUnresolvedArtifactResultVerificationCode()}
+
+            if(expectedMetadataFileNames.size() > 0) {
+                def resolvedArtifactFileNames = resolvedArtifacts*.file.name as Set
+                assert resolvedArtifactFileNames == expectedMetadataFileNames
+            }
+        }
     }
 }
 """
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
index e53f148..af12591 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
@@ -19,8 +19,11 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 import spock.lang.Issue
 
+ at RunWith(FluidDependenciesResolveRunner)
 class ProjectDependenciesIntegrationTest extends AbstractDependencyResolutionTest {
 
     @Issue("GRADLE-2477") //this is a feature on its own but also covers one of the reported issues
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
index a5a9b84..29938e3 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 package org.gradle.integtests.resolve
-
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.junit.runner.RunWith
 import spock.lang.IgnoreIf
 import spock.lang.Issue
 
+ at RunWith(FluidDependenciesResolveRunner)
 class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
-
     public void "project dependency includes artifacts and transitive dependencies of default configuration in target project"() {
         given:
         mavenRepo.module("org.other", "externalA", 1.2).publish()
@@ -107,7 +108,8 @@ project(":b") {
 """
 
         expect:
-        succeeds "check"
+        succeeds ":b:check"
+        executedAndNotSkipped ":a:jar"
     }
 
     public void "project dependency that specifies a target configuration includes artifacts and transitive dependencies of selected configuration"() {
@@ -147,7 +149,8 @@ project(":b") {
 """
 
         expect:
-        succeeds "check"
+        succeeds ":b:check"
+        executedAndNotSkipped ":a:jar"
     }
 
     @Issue("GRADLE-2899")
@@ -183,7 +186,7 @@ project(':b') {
         configB1 project(path:':a', configuration:'configA1')
         configB2 project(path:':a', configuration:'configA2')
     }
-    task check << {
+    task check(dependsOn: [configurations.configB1, configurations.configB2]) << {
         assert configurations.configB1.collect { it.name } == ['A1.jar']
         assert configurations.configB2.collect { it.name } == ['A2.jar']
     }
@@ -191,10 +194,11 @@ project(':b') {
 """
 
         expect:
-        succeeds "check"
+        succeeds ":b:check"
+        executedAndNotSkipped ":a:A1jar", ":a:A2jar"
     }
 
-    public void "resolved project artifacts reflect late changes in project attributes"() {
+    public void "resolved project artifacts reflect project properties changed after task graph is resolved"() {
         given:
         file('settings.gradle') << "include 'a', 'b'"
 
@@ -202,7 +206,9 @@ project(':b') {
         file('a/build.gradle') << '''
             apply plugin: 'base'
             configurations { compile }
+            dependencies { compile project(path: ':b', configuration: 'compile') }
             task aJar(type: Jar) { }
+            gradle.taskGraph.whenReady { project.version = 'late' }
             artifacts { compile aJar }
 '''
         file('b/build.gradle') << '''
@@ -210,19 +216,24 @@ project(':b') {
             version = 'early'
             configurations { compile }
             task bJar(type: Jar) { }
-            gradle.taskGraph.whenReady { project.version = 'late' }
+            gradle.taskGraph.whenReady { project.version = 'transitive-late' }
             artifacts { compile bJar }
 '''
         file('build.gradle') << '''
-            configurations { compile }
-            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
-            task test(dependsOn: configurations.compile) << {
-                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
+            configurations {
+                compile
+                testCompile { extendsFrom compile }
+            }
+            dependencies { compile project(path: ':a', configuration: 'compile') }
+            task test(dependsOn: [configurations.compile, configurations.testCompile]) << {
+                assert configurations.compile.collect { it.name } == ['a-late.jar', 'b-transitive-late.jar']
+                assert configurations.testCompile.collect { it.name } == ['a-late.jar', 'b-transitive-late.jar']
             }
 '''
 
         expect:
-        succeeds "test"
+        succeeds ":test"
+        executedAndNotSkipped ":a:aJar", ":b:bJar"
     }
 
     public void "resolved project artifact can be changed by configuration task"() {
@@ -234,7 +245,8 @@ project(':b') {
             apply plugin: 'base'
             configurations { compile }
             task configureJar << {
-                tasks.aJar.archiveName = "modified-artifact.txt"
+                tasks.aJar.extension = "txt"
+                tasks.aJar.classifier = "modified"
             }
             task aJar(type: Jar) {
                 dependsOn configureJar
@@ -248,18 +260,25 @@ project(':b') {
             }
             dependencies { compile project(path: ':a', configuration: 'compile') }
             task test(dependsOn: [configurations.compile, configurations.testCompile]) << {
-                assert configurations.compile.collect { it.name } == ['modified-artifact.txt']
-                assert configurations.testCompile.collect { it.name } == ['modified-artifact.txt']
+                assert configurations.compile.collect { it.name } == ['a-modified.txt']
+                assert configurations.testCompile.collect { it.name } == ['a-modified.txt']
             }
 '''
 
         expect:
-        succeeds "test"
+        succeeds ":test"
+        executedAndNotSkipped ":a:configureJar", ":a:aJar"
     }
 
-
+    /**
+     * When the set of artifacts for a project is changed during task execution, then a project dependency will not be resolved
+     * fully and/or correctly.
+     * - Without fluid dependencies, the artifacts are included in the resolution result, but the tasks to build them are not executed
+     * - With fluid dependencies, the changed artifacts are _not_ included in the resolution result, nor are the tasks.
+     */
     public void "set of resolved project artifacts can be changed after task graph is resolved"() {
         given:
+        def fluidDependencies = Boolean.getBoolean(FluidDependenciesResolveRunner.ASSUME_FLUID_DEPENDENCIES)
         file('settings.gradle') << "include 'a'"
 
         and:
@@ -274,20 +293,31 @@ project(':b') {
             }
             artifacts { compile tasks.jar1 }
             gradle.taskGraph.whenReady {
-                configurations.compile.artifacts.clear()
                 artifacts { compile tasks.jar2 }
             }
 '''
-        file('build.gradle') << '''
+        file('build.gradle') << """
             configurations { compile }
             dependencies { compile project(path: ':a', configuration: 'compile') }
             task test(dependsOn: configurations.compile) << {
-                assert configurations.compile.collect { it.name } == ['a-2.jar']
+                assert configurations.compile.collect { it.name } == ${fluidDependencies ? "['a-1.jar']" : "['a-1.jar', 'a-2.jar']"}
             }
-'''
+"""
 
-        expect:
-        succeeds "test"
+        when:
+        if (fluidDependencies) {
+            executer.withDeprecationChecksDisabled()
+        }
+        succeeds ":test"
+
+        then:
+        // The added artifact is never added as a task
+        executedAndNotSkipped ":a:jar1" // Should include ":a:jar2" when no fluidDependencies
+
+        and:
+        if (fluidDependencies) {
+            output.contains "Changed artifacts of configuration ':a:compile' after it has been included in dependency resolution"
+        }
     }
 
     public void "project dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
@@ -325,7 +355,10 @@ project(":b") {
 """
 
         expect:
-        succeeds 'test'
+        succeeds 'b:test'
+
+        // Demonstrates superfluous task dependencies for project artifacts
+        executedAndNotSkipped ":a:aJar", ":a:bJar" // Should be only the ":a:aJar"
     }
 
     public void "reports project dependency that refers to an unknown artifact"() {
@@ -354,7 +387,7 @@ project(":b") {
 """
 
         expect:
-        fails 'test'
+        fails ':b:test'
 
         and:
         failure.assertResolutionFailure(":b:compile").assertHasCause("Could not find b.jar (test:a:unspecified).")
@@ -383,14 +416,15 @@ project(':b') {
     dependencies {
         compile project(':a'), { transitive = false }
     }
-    task listJars << {
+    task listJars(dependsOn: configurations.compile) << {
         assert configurations.compile.collect { it.name } == ['a.jar']
     }
 }
 '''
 
         expect:
-        succeeds "listJars"
+        succeeds ":b:listJars"
+        executedAndNotSkipped ":a:jar"
     }
 
     public void "can have cycle in project dependencies"() {
@@ -453,7 +487,8 @@ project('c') {
 """
 
         expect:
-        succeeds "listJars"
+        succeeds ":a:listJars"
+        executedAndNotSkipped ":b:jar", ":c:jar"
     }
 
     // this test is largely covered by other tests, but does ensure that there is nothing special about
@@ -466,7 +501,7 @@ project('c') {
         file("a/build.gradle") << """
             group = "g"
             version = 1.0
-            
+
             apply plugin: 'base'
             task zip(type: Zip) {
                 from "some.txt"
@@ -481,20 +516,59 @@ project('c') {
             dependencies {
                 conf project(":a")
             }
-            
+
             task copyZip(type: Copy) {
                 from configurations.conf
                 into "\$buildDir/copied"
             }
         """
-        
+
         when:
         succeeds ":b:copyZip"
-        
+
         then:
-        ":b:copyZip" in nonSkippedTasks
-        
+        executedAndNotSkipped ":a:zip", ":b:copyZip"
+
         and:
         file("b/build/copied/a-1.0.zip").exists()
     }
+
+    def "resolving configuration with project dependency marks dependency's configuration as observed"() {
+        settingsFile << "include 'api'; include 'impl'"
+
+        buildFile << """
+            allprojects {
+                configurations {
+                    conf
+                }
+                configurations.create("default").extendsFrom(configurations.conf)
+            }
+
+            project(":impl") {
+                dependencies {
+                    conf project(":api")
+                }
+
+                task check << {
+                    assert configurations.conf.state == Configuration.State.UNRESOLVED
+                    assert project(":api").configurations.conf.state == Configuration.State.UNRESOLVED
+
+                    configurations.conf.resolve()
+
+                    assert configurations.conf.state == Configuration.State.RESOLVED
+                    assert project(":api").configurations.conf.state == Configuration.State.UNRESOLVED
+
+                    // Attempt to change the configuration, to demonstrate that is has been observed
+                    project(":api").configurations.conf.dependencies.add(null)
+                }
+            }
+"""
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        succeeds("impl:check")
+
+        then:
+        output.contains "Changed dependencies of configuration ':api:conf' after it has been included in dependency resolution"
+    }
 }
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/PublishAndResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/PublishAndResolveIntegrationTest.groovy
new file mode 100644
index 0000000..937f5db
--- /dev/null
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/PublishAndResolveIntegrationTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ivy.IvyFileRepository
+import org.gradle.util.TextUtil
+
+class PublishAndResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def setup() {
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            allprojects {
+                apply plugin: 'java'
+                apply plugin: 'ivy-publish'
+
+                group = 'org.gradle.test'
+                version = '1.9'
+
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+            }
+        """
+    }
+
+    def "can resolve static dependency published by a dependent task in the same project"() {
+        given:
+        buildFile << """
+            ${taskWhichPublishes('api', '1.1')}
+            ${taskWhichResolves('api', '1.1')}
+        """
+
+        expect:
+        succeeds resolveTask
+        versionIsCopiedAndExists("api", "1.1")
+    }
+
+    def "can resolve static dependency published by a dependent task in another project in the same build"() {
+        settingsFile << """
+            include ':child'
+        """
+
+        given:
+        buildFile << """
+            ${taskWhichPublishes('api', '1.1')}
+            project(':child') {
+                ${taskWhichResolves('api', '1.1')}
+            }
+        """
+
+        expect:
+        succeeds ":child:${resolveTask}"
+        versionIsCopiedAndExists("api", "1.1", "child/")
+    }
+
+    def "can resolve static dependency published by UploadArchives task in another project in the same build"() {
+        settingsFile << """
+            include ':child'
+        """
+
+        given:
+        buildFile << """
+            uploadArchives {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+            }
+            project(':child') {
+                ${taskWhichResolves('root', '1.9')}
+                ${resolveTask}.dependsOn ":uploadArchives"
+            }
+        """
+
+        expect:
+        succeeds ":child:${resolveTask}"
+        versionIsCopiedAndExists("root", "1.9", "child/")
+    }
+
+    def "can resolve dynamic dependency published by a dependent task"() {
+        ivyRepo.module('org.gradle.test', 'api', '1.0').publish()
+
+        given:
+        buildFile << """
+            ${taskWhichPublishes('api', '1.1')}
+            ${taskWhichResolves('api', '1.+')}
+        """
+
+        expect:
+        succeeds resolveTask
+        versionIsCopiedAndExists("api", "1.1")
+    }
+
+    def "can resolve dependency published by a custom publishing task"() {
+        def tmpRepo = new IvyFileRepository(file("tmp-repo"))
+        tmpRepo.module('org.gradle.test', 'api', '1.1').publish()
+
+        given:
+        buildFile << """
+            task customPublish(type: Copy) {
+                from "${safePath(tmpRepo.moduleDir('org.gradle.test', 'api'), '1.1')}"
+                into "${safePath(ivyRepo.moduleDir('org.gradle.test', 'api'), '1.1')}"
+            }
+            ${taskWhichResolves('api', '1.1')}
+            ${resolveTask}.dependsOn customPublish
+        """
+
+        expect:
+        succeeds resolveTask
+        versionIsCopiedAndExists("api", "1.1")
+    }
+
+    def safePath(Object... paths) {
+        return TextUtil.escapeString(paths.join(File.separator))
+    }
+
+    def versionIsCopiedAndExists(lib, version, root="") {
+        assert TextUtil.normaliseFileSeparators(output).contains("ivy-repo/org.gradle.test/${lib}/${version}/${lib}-${version}.jar")
+        testDirectory.assertContainsDescendants("${root}build/copies/${lib}-${version}.jar")
+        true
+    }
+
+    def resolveTask = "resolveAndCopy"
+
+    def taskWhichPublishes(lib, version) {
+        return """
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+                publications {
+                    jar(IvyPublication) {
+                        from components.java
+                        organisation group
+                        module '${lib}'
+                        revision '${version}'
+                    }
+                }
+            }
+        """
+    }
+
+    def taskWhichResolves(lib, resolveVersion) {
+        return """
+            configurations {
+                testartifacts
+            }
+
+            dependencies {
+                testartifacts 'org.gradle.test:${lib}:${resolveVersion}'
+            }
+
+            task ${resolveTask}(type: Copy) {
+                dependsOn ":publish"
+                from {
+                    configurations.testartifacts
+                }
+                into "\${buildDir}/copies"
+                eachFile { println it.file.path - rootDir.path }
+            }
+        """
+    }
+}
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy
index 18ae13f..89b61bb 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionResultApiIntegrationTest.groovy
@@ -19,9 +19,12 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.junit.runner.RunWith
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
+ at RunWith(FluidDependenciesResolveRunner)
 class ResolutionResultApiIntegrationTest extends AbstractDependencyResolutionTest {
 
     /*
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
index e12949f..30396a5 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
@@ -20,10 +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.integtests.fixtures.FluidDependenciesResolveRunner
 import org.gradle.util.TestUtil
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 
+ at RunWith(FluidDependenciesResolveRunner)
 public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTest {
     def DefaultProject project = TestUtil.createRootProject()
     def Project childProject = TestUtil.createChildProject(project, "child", new File("."))
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/UnsupportedConfigurationMutationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/UnsupportedConfigurationMutationTest.groovy
index a112295..aeb6587 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/UnsupportedConfigurationMutationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/UnsupportedConfigurationMutationTest.groovy
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-
-
 package org.gradle.integtests.resolve
-
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.junit.runner.RunWith
 import spock.lang.Issue
 
-// TODO - report on the configuration that was actually changed
-// TODO - warn about configurations resolved via a project dependency
-// TODO - verify line number is included in deprecation message
-// TODO - warn about changes to artifacts
-// TODO - warn about changes to resolution strategy and other mutations
+ at RunWith(FluidDependenciesResolveRunner)
 class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
 
     def "does not allow adding dependencies to a configuration that has been resolved"() {
@@ -35,7 +31,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
             dependencies { a files("some.jar") }
         """
         when: fails()
-        then: failure.assertHasCause("Cannot change configuration ':a' after it has been resolved.")
+        then: failure.assertHasCause("Cannot change dependencies of configuration ':a' after it has been resolved.")
     }
 
     def "does not allow adding artifacts to a configuration that has been resolved"() {
@@ -45,7 +41,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
             artifacts { a file("some.jar") }
         """
         when: fails()
-        then: failure.assertHasCause("Cannot change configuration ':a' after it has been resolved.")
+        then: failure.assertHasCause("Cannot change artifacts of configuration ':a' after it has been resolved.")
     }
 
     def "does not allow changing excludes on a configuration that has been resolved"() {
@@ -55,7 +51,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
             configurations.a.exclude group: 'someGroup'
         """
         when: fails()
-        then: failure.assertHasCause("Cannot change configuration ':a' after it has been resolved.")
+        then: failure.assertHasCause("Cannot change dependencies of configuration ':a' after it has been resolved.")
     }
 
     def "warns about changing conflict resolution on a configuration that has been resolved"() {
@@ -67,7 +63,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     def "warns about changing forced versions on a configuration that has been resolved"() {
@@ -79,7 +75,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     def "warns about changing cache policy on a configuration that has been resolved"() {
@@ -91,7 +87,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     def "warns about changing resolution rules on a configuration that has been resolved"() {
@@ -103,7 +99,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     def "warns about changing substitution rules on a configuration that has been resolved"() {
@@ -115,7 +111,7 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     def "warns about changing component selection rules on a configuration that has been resolved"() {
@@ -127,7 +123,141 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed strategy of configuration ':a' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+    }
+
+    def "warns about changing dependencies of a configuration that has been resolved for task dependencies"() {
+        mavenRepo.module("org.utils", "extra", '1.5').publish()
+
+        settingsFile << "include 'api', 'impl'"
+        buildFile << """
+            allprojects {
+                apply plugin: "java"
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                configurations.all {
+                    resolutionStrategy.assumeFluidDependencies()
+                }
+            }
+
+            project(":api") {
+                task addDependency << {
+                   dependencies {
+                        compile "org.utils:extra:1.5"
+                    }
+                }
+            }
+
+            project(":impl") {
+                dependencies {
+                    compile project(":api")
+                }
+
+                task addDependency << {
+                    dependencies {
+                        compile "org.utils:extra:1.5"
+                    }
+                }
+
+                task modifyConfigDuringTaskExecution(dependsOn: [':impl:addDependency', configurations.compile]) << {
+                    def files = configurations.compile.files
+                    assert files*.name.sort() == ["api.jar", "extra-1.5.jar"]
+                    assert files*.exists() == [ true, true ]
+                }
+                task modifyParentConfigDuringTaskExecution(dependsOn: [':impl:addDependency', configurations.testCompile]) << {
+                    def files = configurations.testCompile.files
+                    assert files*.name.sort() == ["api.jar", "extra-1.5.jar"]
+                    assert files*.exists() == [ true, true ]
+                }
+                task modifyDependentConfigDuringTaskExecution(dependsOn: [':api:addDependency', configurations.compile]) << {
+                    def files = configurations.compile.files
+                    assert files*.name.sort() == ["api.jar"] // Late dependency is not honoured
+                    assert files*.exists() == [ true ]
+                }
+            }
+"""
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        succeeds("impl:modifyConfigDuringTaskExecution")
+
+        then:
+        output.contains("Changed dependencies of configuration ':impl:compile' after task dependencies have been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        output.contains("Resolving configuration ':impl:compile' again after modification.")
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        succeeds("impl:modifyParentConfigDuringTaskExecution")
+
+        then:
+        output.contains("Changed dependencies of configuration ':impl:compile' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        output.contains("Changed dependencies of parent of configuration ':impl:testCompile' after task dependencies have been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        output.contains("Resolving configuration ':impl:testCompile' again after modification.")
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        succeeds("impl:modifyDependentConfigDuringTaskExecution")
+
+        then:
+        output.contains("Changed dependencies of configuration ':api:compile' after task dependencies have been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+    }
+
+    def "warns about changing artifacts of a configuration that has been resolved for task dependencies"() {
+        mavenRepo.module("org.utils", "extra", '1.5').publish()
+
+        settingsFile << "include 'api', 'impl'"
+        buildFile << """
+            allprojects {
+                apply plugin: "java"
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                configurations.all {
+                    resolutionStrategy.assumeFluidDependencies()
+                }
+            }
+
+            project(":api") {
+                task addArtifact << {
+                    artifacts { compile file("some.jar") }
+                }
+            }
+
+            project(":impl") {
+                dependencies {
+                    compile project(":api")
+                }
+                task addArtifact << {
+                    artifacts { compile file("some.jar") }
+                }
+
+                task addArtifactToConfigDuringTaskExecution(dependsOn: [':impl:addArtifact', configurations.compile])
+                task addArtifactToParentConfigDuringTaskExecution(dependsOn: [':impl:addArtifact', configurations.testCompile])
+                task addArtifactToDependentConfigDuringTaskExecution(dependsOn: [':api:addArtifact', configurations.compile])
+            }
+"""
+
+        when:
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds("impl:addArtifactToConfigDuringTaskExecution")
+        output.contains("Changed artifacts of configuration ':impl:compile' after task dependencies have been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+
+        when:
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds("impl:addArtifactToParentConfigDuringTaskExecution")
+        output.contains("Changed artifacts of configuration ':impl:compile' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+
+        when:
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds("impl:addArtifactToDependentConfigDuringTaskExecution")
+        output.contains("Changed artifacts of configuration ':api:compile' after task dependencies have been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     @Issue("GRADLE-3155")
@@ -144,7 +274,8 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed dependencies of configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        and:  output.contains("Changed dependencies of parent of configuration ':c' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     @Issue("GRADLE-3155")
@@ -161,7 +292,8 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed artifacts of configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        and:  output.contains("Changed artifacts of parent of configuration ':c' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     @Issue("GRADLE-3155")
@@ -178,7 +310,27 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: succeeds()
-        then: output.contains("Attempting to change configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        then: output.contains("Changed dependencies of configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        and:  output.contains("Changed dependencies of parent of configuration ':c' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+    }
+
+    def "allows changing resolution stragegy of a configuration whose child has been resolved"() {
+        buildFile << """
+            configurations {
+                a
+                b.extendsFrom a
+                c.extendsFrom b
+            }
+            configurations.c.resolve()
+            configurations.a.resolutionStrategy.failOnVersionConflict()
+            configurations.a.resolutionStrategy.force "org.utils:api:1.3"
+            configurations.a.resolutionStrategy.forcedModules = [ "org.utils:api:1.4" ]
+            configurations.a.resolutionStrategy.eachDependency {}
+            configurations.a.resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
+            configurations.a.resolutionStrategy.cacheChangingModulesFor 0, "seconds"
+            configurations.a.resolutionStrategy.componentSelection.all {}
+        """
+        expect: succeeds()
     }
 
     def "warning is upgraded to an error when configuration is resolved"() {
@@ -195,8 +347,9 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         executer.withDeprecationChecksDisabled()
 
         when: fails()
-        then: output.contains("Attempting to change configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
-        and: failure.assertHasCause("Cannot change configuration ':a' after it has been resolved.")
+        then: output.contains("Changed dependencies of configuration ':a' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        and: output.contains("Changed dependencies of parent of configuration ':b' after it has been resolved. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+        and: failure.assertHasCause("Cannot change dependencies of configuration ':a' after it has been resolved.")
     }
 
     @Issue("GRADLE-3155")
@@ -224,4 +377,77 @@ class UnsupportedConfigurationMutationTest extends AbstractIntegrationSpec {
         """
         expect: succeeds()
     }
+
+    def "allows changing a non-empty configuration that does not affect a resolved configuration"() {
+        buildFile << """
+            configurations {
+                a
+                b
+            }
+            dependencies { b files("some.jar") }
+            configurations.b.resolve()
+            dependencies { a "a:b:c" }
+        """
+        expect: succeeds()
+    }
+
+    def "warns about changing a dependency project's dependencies after included in resolution"() {
+        settingsFile << "include 'api', 'impl'"
+        buildFile << """
+            allprojects {
+                apply plugin: "java"
+            }
+            dependencies {
+                compile project(":impl")
+            }
+            project(":impl") {
+                dependencies {
+                    compile project(":api")
+                }
+            }
+            configurations.compile.resolve()
+            project(":api") {
+                dependencies {
+                    compile files("some.jar")
+                }
+            }
+"""
+        executer.withDeprecationChecksDisabled()
+
+        when: succeeds()
+        then: output.contains("Changed dependencies of configuration ':api:compile' after it has been included in dependency resolution. This behaviour has been deprecated and is scheduled to be removed in Gradle 3.0")
+    }
+
+    @Issue("GRADLE-3297")
+    def "using offline flag does not emit deprecation warning when child configuration is explicitly resolved"() {
+        def repo = new MavenFileRepository(file("repo"))
+        repo.module('org.test', 'moduleA', '1.0').publish()
+
+        buildFile << """
+configurations {
+    parentConfig
+    childConfig.extendsFrom parentConfig
+}
+dependencies {
+  // Parent must have at least 1 dependency to force resolution
+  parentConfig "org.test:moduleA:1.0"
+}
+repositories {
+    maven { url '$repo.uri' }
+}
+
+task resolveChildFirst {
+    doLast {
+        configurations.childConfig.resolve()
+        configurations.parentConfig.resolve()
+    }
+}
+        """
+
+        when:
+        executer.withArguments("--offline")
+
+        then:
+        succeeds("resolveChildFirst")
+    }
 }
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorDependencyExcludeResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorDependencyExcludeResolveIntegrationTest.groovy
index 5964a5d..a1224ca 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorDependencyExcludeResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorDependencyExcludeResolveIntegrationTest.groovy
@@ -48,11 +48,13 @@ class IvyDescriptorDependencyExcludeResolveIntegrationTest extends AbstractIvyDe
         assertResolvedFiles(resolvedJars)
 
         where:
-        name                    | excludeAttributes | resolvedJars
-        'non-matching module'   | [module: 'other'] | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
-        'non-matching artifact' | [name: 'other']   | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
-        'matching module'       | [module: 'b']     | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar'] // Module exclude does not apply to declaring module
-        'matching artifact'     | [name: 'b']       | ['a-1.0.jar', 'c-1.0.jar'] // Artifact exclude does apply to declaring module
+        name                           | excludeAttributes | resolvedJars
+        'non-matching module'          | [module: 'other'] | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
+        'non-matching artifact'        | [name: 'other']   | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
+        'module on other dependency'   | [module: 'c']     | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
+        'artifact on other dependency' | [name: 'c']       | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar']
+        'matching module'              | [module: 'b']     | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar'] // Module exclude does not apply to declaring module
+        'matching artifact'            | [name: 'b']       | ['a-1.0.jar', 'c-1.0.jar'] // Artifact exclude does apply to declaring module
     }
 
     /**
@@ -90,6 +92,36 @@ class IvyDescriptorDependencyExcludeResolveIntegrationTest extends AbstractIvyDe
         'matching module'       | [module: 'd']     | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar', 'e-1.0.jar']
         'matching artifact'     | [name: 'd']       | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar', 'e-1.0.jar']
     }
+    /**
+     * Exclude of transitive dependency involved in a dependency cycle.
+     *
+     * Dependency graph:
+     * a -> b -> c -> d -> c
+     *
+     * 'c' is excluded on dependency a->b
+     */
+    @Unroll
+    def "module involved in dependency cycle with excluded #name"() {
+        given:
+        IvyModule moduleA = ivyRepo.module('a').dependsOn('b')
+        addExcludeRuleToModuleDependency(moduleA, 'b', excludeAttributes)
+        moduleA.publish()
+        ivyRepo.module('b').dependsOn('c').publish()
+        ivyRepo.module('c').dependsOn('d').publish()
+        ivyRepo.module('d').dependsOn('c').publish()
+
+        when:
+        succeedsDependencyResolution()
+
+        then:
+        assertResolvedFiles(resolvedJars)
+
+        where:
+        name               | excludeAttributes | resolvedJars
+        'same module'      | [module: 'b']     | ['a-1.0.jar', 'b-1.0.jar', 'c-1.0.jar', 'd-1.0.jar']
+        'dependent module' | [module: 'c']     | ['a-1.0.jar', 'b-1.0.jar']
+        'artifact'         | [name: 'c']       | ['a-1.0.jar', 'b-1.0.jar', 'd-1.0.jar']
+    }
 
     /**
      * Exclude of transitive dependency with a single artifact does not exclude its transitive module by using a combination of name exclude rules.
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
index 12daa3c..71bdb40 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
@@ -17,7 +17,9 @@
 package org.gradle.integtests.resolve.ivy
 
 import org.gradle.integtests.resolve.http.AbstractHttpsRepoResolveIntegrationTest
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class IvyHttpsRepoResolveIntegrationTest extends AbstractHttpsRepoResolveIntegrationTest {
     protected String setupRepo() {
         def module = ivyHttpRepo('repo1').module('my-group', 'my-module').publish()
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleArtifactResolutionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleArtifactResolutionIntegrationTest.groovy
index a79b352..7621fb3 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleArtifactResolutionIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyModuleArtifactResolutionIntegrationTest.groovy
@@ -83,7 +83,7 @@ repositories {
 
         when:
         fixture.requestComponent('IvyModule').requestArtifact('IvyDescriptorArtifact')
-               .expectUnresolvedComponentResult(new IllegalArgumentException("Cannot query artifacts for a project component (project :)"))
+               .expectUnresolvedComponentResult(new IllegalArgumentException("Cannot query artifacts for a project component (project :)."))
                .expectNoMetadataFiles()
                .createVerifyTaskForProjectComponentIdentifier()
 
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
index 1435787..13d6039 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
@@ -17,7 +17,9 @@
 package org.gradle.integtests.resolve.maven
 
 import org.gradle.integtests.resolve.http.AbstractHttpsRepoResolveIntegrationTest
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class MavenHttpsRepoResolveIntegrationTest extends AbstractHttpsRepoResolveIntegrationTest {
     protected String setupRepo() {
         def module = mavenRepo().module('my-group', 'my-module').publish()
diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenModuleArtifactResolutionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenModuleArtifactResolutionIntegrationTest.groovy
index b5fef22..ec64db4 100644
--- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenModuleArtifactResolutionIntegrationTest.groovy
+++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenModuleArtifactResolutionIntegrationTest.groovy
@@ -81,7 +81,7 @@ repositories {
 
         when:
         fixture.requestComponent('MavenModule').requestArtifact('MavenPomArtifact')
-               .expectUnresolvedComponentResult(new IllegalArgumentException("Cannot query artifacts for a project component (project :)"))
+               .expectUnresolvedComponentResult(new IllegalArgumentException("Cannot query artifacts for a project component (project :)."))
                .expectNoMetadataFiles()
                .createVerifyTaskForProjectComponentIdentifier()
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
index cdbadd3..a7d7f10 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
@@ -15,14 +15,19 @@
  */
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.artifacts.ResolveContext;
 import org.gradle.api.artifacts.ResolveException;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 
 import java.util.List;
 
 public interface ArtifactDependencyResolver {
-    void resolve(ConfigurationInternal configuration,
+    void resolve(ResolveContext resolveContext,
+                 List<? extends ResolutionAwareRepository> repositories,
+                 GlobalDependencyResolutionRules metadataHandler,
+                 ResolverResults results) throws ResolveException;
+
+    void resolveArtifacts(ResolveContext resolveContext,
                  List<? extends ResolutionAwareRepository> repositories,
                  GlobalDependencyResolutionRules metadataHandler,
                  ResolverResults results) throws ResolveException;
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ConfigurationResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ConfigurationResolver.java
index 0751e90..1ef357b 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ConfigurationResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ConfigurationResolver.java
@@ -19,5 +19,6 @@ import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 
 public interface ConfigurationResolver {
-    ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException;
+    void resolve(ConfigurationInternal configuration, ResolverResults results) throws ResolveException;
+    void resolveArtifacts(ConfigurationInternal configuration, ResolverResults results) throws ResolveException;
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
index bedc259..21ef60a 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -93,14 +93,15 @@ public class DefaultDependencyManagementServices implements DependencyManagement
         }
 
         ConfigurationContainerInternal createConfigurationContainer(Instantiator instantiator, ConfigurationResolver configurationResolver, DomainObjectContext domainObjectContext,
-                                                                    ListenerManager listenerManager, DependencyMetaDataProvider metaDataProvider, ProjectAccessListener projectAccessListener) {
+                                                                    ListenerManager listenerManager, DependencyMetaDataProvider metaDataProvider, ProjectAccessListener projectAccessListener, ProjectFinder projectFinder) {
             return instantiator.newInstance(DefaultConfigurationContainer.class,
                     configurationResolver,
                     instantiator,
                     domainObjectContext,
                     listenerManager,
                     metaDataProvider,
-                    projectAccessListener);
+                    projectAccessListener,
+                    projectFinder);
         }
 
         DependencyHandler createDependencyHandler(Instantiator instantiator, ConfigurationContainerInternal configurationContainer, DependencyFactory dependencyFactory,
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
index 5660d57..262d638 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
@@ -25,21 +25,15 @@ import java.io.File;
 public class DefaultResolvedArtifact implements ResolvedArtifact {
     private final ResolvedModuleVersion owner;
     private final IvyArtifactName artifact;
-    private long id;
     private Factory<File> artifactSource;
     private File file;
 
-    public DefaultResolvedArtifact(ResolvedModuleVersion owner, IvyArtifactName artifact, Factory<File> artifactSource, long id) {
+    public DefaultResolvedArtifact(ResolvedModuleVersion owner, IvyArtifactName artifact, Factory<File> artifactSource) {
         this.owner = owner;
         this.artifact = artifact;
-        this.id = id;
         this.artifactSource = artifactSource;
     }
 
-    public long getId() {
-        return id;
-    }
-
     public ResolvedModuleVersion getModuleVersion() {
         return owner;
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java
index 693f391..8b25479 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementBuildScopeServices.java
@@ -204,7 +204,8 @@ class DependencyManagementBuildScopeServices {
 
     ArtifactDependencyResolver createArtifactDependencyResolver(ResolveIvyFactory resolveIvyFactory, LocalComponentFactory publishModuleDescriptorConverter, DependencyDescriptorFactory dependencyDescriptorFactory,
                                                                 CacheLockingManager cacheLockingManager, IvyContextManager ivyContextManager, ResolutionResultsStoreFactory resolutionResultsStoreFactory,
-                                                                VersionComparator versionComparator, ProjectRegistry<ProjectInternal> projectRegistry, ComponentIdentifierFactory componentIdentifierFactory) {
+                                                                VersionComparator versionComparator, ProjectRegistry<ProjectInternal> projectRegistry, ComponentIdentifierFactory componentIdentifierFactory,
+                                                                StartParameter startParameter) {
         ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
                 resolveIvyFactory,
                 publishModuleDescriptorConverter,
@@ -215,7 +216,8 @@ class DependencyManagementBuildScopeServices {
                 cacheLockingManager,
                 ivyContextManager,
                 resolutionResultsStoreFactory,
-                versionComparator
+                versionComparator,
+                startParameter.isBuildProjectDependencies()
         );
         return new ErrorHandlingArtifactDependencyResolver(
                 new ShortcircuitEmptyConfigsArtifactDependencyResolver(
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java
index 65ce1fc..3cc1953 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java
@@ -20,7 +20,6 @@ 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.LocalComponentFactory;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
 
@@ -52,17 +51,17 @@ class DependencyManagementGlobalScopeServices {
                 descriptorFactory);
     }
 
-    LocalComponentFactory createPublishLocalComponentFactory(ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+    CompositeResolveLocalComponentFactory createPublishLocalComponentFactory(ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
                                                              DependencyDescriptorFactory dependencyDescriptorFactory,
                                                              ExcludeRuleConverter excludeRuleConverter,
                                                              ComponentIdentifierFactory componentIdentifierFactory) {
-        return new ResolveLocalComponentFactory(
+        return new CompositeResolveLocalComponentFactory(new ResolveLocalComponentFactory(
                 configurationsToModuleDescriptorConverter,
                 new DefaultDependenciesToModuleDescriptorConverter(
                         dependencyDescriptorFactory,
                         excludeRuleConverter),
                 componentIdentifierFactory,
-                new DefaultConfigurationsToArtifactsConverter());
+                new DefaultConfigurationsToArtifactsConverter()));
 
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolveContextInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolveContextInternal.java
new file mode 100644
index 0000000..7fa45ec
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolveContextInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.ResolveContext;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
+
+public interface ResolveContextInternal extends ResolveContext {
+    ProjectDependencyResolver newProjectDependencyResolver(
+        ProjectComponentRegistry projectComponentRegistry,
+        LocalComponentFactory localComponentFactory,
+        DependencyToComponentIdResolver delegateIdResolver,
+        ComponentMetaDataResolver delegateComponentResolver);
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java
index 5b22d63..2a853e8 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java
@@ -16,9 +16,14 @@
 
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.Action;
 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.ivyservice.resolveengine.oldresult.ResolvedArtifactsBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedGraphResults;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResultsBuilder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfiguration;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResults;
 
 public class ResolverResults {
@@ -26,10 +31,26 @@ public class ResolverResults {
     private ResolutionResult resolutionResult;
     private ResolveException fatalFailure;
     private ResolvedProjectConfigurationResults resolvedProjectConfigurationResults;
+    private TransientConfigurationResultsBuilder transientConfigurationResultsBuilder;
+    private ResolvedGraphResults graphResults;
+    private ResolvedArtifactsBuilder artifactResults;
+
+    public boolean hasError() {
+        if (fatalFailure != null) {
+            return true;
+        }
+        if (graphResults != null && graphResults.hasError()) {
+            return true;
+        }
+        if (resolvedConfiguration != null && resolvedConfiguration.hasError()) {
+            return true;
+        }
+        return false;
+    }
 
     //old model, slowly being replaced by the new model
     public ResolvedConfiguration getResolvedConfiguration() {
-        assertHasResult();
+        assertHasArtifacts();
         return resolvedConfiguration;
     }
 
@@ -42,34 +63,65 @@ public class ResolverResults {
         return resolutionResult;
     }
 
-    public ResolvedProjectConfigurationResults getResolvedProjectConfigurationResults() {
+    public void eachResolvedProject(Action<ResolvedProjectConfiguration> action) {
         assertHasResult();
         if (fatalFailure != null) {
             throw fatalFailure;
         }
+        for (ResolvedProjectConfiguration resolvedProjectConfiguration : resolvedProjectConfigurationResults.get()) {
+            action.execute(resolvedProjectConfiguration);
+        }
+    }
+
+    public ResolvedProjectConfigurationResults getResolvedProjectConfigurationResults() {
         return resolvedProjectConfigurationResults;
     }
 
     private void assertHasResult() {
-        if (resolvedConfiguration == null) {
+        if (resolutionResult == null && fatalFailure == null) {
             throw new IllegalStateException("Resolution result has not been attached.");
         }
     }
 
-    public void withResolvedConfiguration(ResolvedConfiguration resolvedConfiguration) {
-        this.resolvedConfiguration = resolvedConfiguration;
+    private void assertHasArtifacts() {
+        if (resolvedConfiguration == null) {
+            throw new IllegalStateException("Resolution artifacts have not been attached.");
+        }
     }
 
-    public void resolved(ResolvedConfiguration resolvedConfiguration, ResolutionResult resolutionResult, ResolvedProjectConfigurationResults resolvedProjectConfigurationResults) {
-        this.resolvedConfiguration = resolvedConfiguration;
+    public void resolved(ResolutionResult resolutionResult, ResolvedProjectConfigurationResults resolvedProjectConfigurationResults) {
         this.resolutionResult = resolutionResult;
         this.resolvedProjectConfigurationResults = resolvedProjectConfigurationResults;
         this.fatalFailure = null;
     }
 
-    public void failed(ResolvedConfiguration resolvedConfiguration, ResolveException failure) {
-        this.resolvedConfiguration = resolvedConfiguration;
+    public void failed(ResolveException failure) {
         this.resolutionResult = null;
         this.fatalFailure = failure;
     }
+
+    public void retainState(ResolvedGraphResults graphResults, ResolvedArtifactsBuilder artifactResults, TransientConfigurationResultsBuilder transientConfigurationResultsBuilder) {
+        this.graphResults = graphResults;
+        this.artifactResults = artifactResults;
+        this.transientConfigurationResultsBuilder = transientConfigurationResultsBuilder;
+    }
+
+    public void withResolvedConfiguration(ResolvedConfiguration resolvedConfiguration) {
+        this.resolvedConfiguration = resolvedConfiguration;
+        this.graphResults = null;
+        this.transientConfigurationResultsBuilder = null;
+        this.artifactResults = null;
+    }
+
+    public ResolvedGraphResults getGraphResults() {
+        return graphResults;
+    }
+
+    public ResolvedArtifactsBuilder getArtifactsBuilder() {
+        return artifactResults;
+    }
+
+    public TransientConfigurationResultsBuilder getTransientConfigurationResultsBuilder() {
+        return transientConfigurationResultsBuilder;
+    }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java
index 20023fe..0bfe7e4 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java
@@ -16,11 +16,22 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolveContext;
+
+public interface ConfigurationInternal extends ResolveContext, Configuration, DependencyMetaDataProvider {
+    enum InternalState {UNRESOLVED, TASK_DEPENDENCIES_RESOLVED, RESULTS_RESOLVED}
 
-public interface ConfigurationInternal extends Configuration, DependencyMetaDataProvider {
     ResolutionStrategyInternal getResolutionStrategy();
 
+    InternalState getResolvedState();
+
     String getPath();
 
-    void includedInResolveResult();
-}
\ No newline at end of file
+    void triggerWhenEmptyActionsIfNecessary();
+
+    void markAsObserved(InternalState requestedState);
+
+    void addMutationValidator(MutationValidator validator);
+
+    void removeMutationValidator(MutationValidator validator);
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
index e06b164..835dd1e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.artifacts.configurations;
 
+import com.google.common.collect.Sets;
 import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.DomainObjectSet;
@@ -26,15 +27,21 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.CompositeDomainObjectSet;
 import org.gradle.api.internal.DefaultDomainObjectSet;
 import org.gradle.api.internal.artifacts.*;
-import org.gradle.api.internal.artifacts.configurations.MutationValidator.MutationType;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfiguration;
 import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.initialization.ProjectAccessListener;
-import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.internal.event.ListenerBroadcast;
 import org.gradle.internal.event.ListenerManager;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
@@ -45,42 +52,59 @@ import java.util.*;
 
 import static org.apache.ivy.core.module.descriptor.Configuration.Visibility;
 
-public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal {
-
-    private final String path;
-    private final String name;
-
-    private Visibility visibility = Visibility.PUBLIC;
-    private boolean transitive = true;
-    private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
-    private String description;
-    private ConfigurationsProvider configurationsProvider;
+public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal, MutationValidator {
     private final ConfigurationResolver resolver;
     private final ListenerManager listenerManager;
     private final DependencyMetaDataProvider metaDataProvider;
     private final DefaultDependencySet dependencies;
     private final CompositeDomainObjectSet<Dependency> inheritedDependencies;
     private final DefaultDependencySet allDependencies;
+    private final List<Action<? super DependencySet>> defaultDependencyActions = new ArrayList<Action<? super DependencySet>>();
     private final DefaultPublishArtifactSet artifacts;
     private final CompositeDomainObjectSet<PublishArtifact> inheritedArtifacts;
     private final DefaultPublishArtifactSet allArtifacts;
     private final ConfigurationResolvableDependencies resolvableDependencies = new ConfigurationResolvableDependencies();
-    private final ListenerBroadcast<DependencyResolutionListener> resolutionListenerBroadcast;
-    private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
+    private final ListenerBroadcast<DependencyResolutionListener> dependencyResolutionListeners;
     private final ProjectAccessListener projectAccessListener;
-
-    // This lock only protects the following fields
-    private final Object lock = new Object();
-    private State state = State.UNRESOLVED;
-    private boolean includedInResult;
-    private ResolverResults cachedResolverResults;
+    private final ProjectFinder projectFinder;
     private final ResolutionStrategyInternal resolutionStrategy;
 
+    private final Set<MutationValidator> childMutationValidators = Sets.newHashSet();
+    private final MutationValidator parentMutationValidator = new MutationValidator() {
+        @Override
+        public void validateMutation(MutationType type) {
+            DefaultConfiguration.this.validateParentMutation(type);
+        }
+    };
+
+    // These fields are not covered by mutation lock
+    private final String path;
+    private final String name;
+
+    private Visibility visibility = Visibility.PUBLIC;
+    private boolean transitive = true;
+    private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
+    private String description;
+    private ConfigurationsProvider configurationsProvider;
+    private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
+
+    private final Object observationLock = new Object();
+    private InternalState observedState = InternalState.UNRESOLVED;
+    private final Object resolutionLock = new Object();
+    private InternalState resolvedState = InternalState.UNRESOLVED;
+    private boolean insideBeforeResolve;
+
+    private ResolverResults cachedResolverResults = new ResolverResults();
+
+    // TODO:DAZ These should really be protected by the lock as well
+    private boolean dependenciesModified;
+
     public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
                                 ConfigurationResolver resolver, ListenerManager listenerManager,
                                 DependencyMetaDataProvider metaDataProvider,
                                 ResolutionStrategyInternal resolutionStrategy,
-                                ProjectAccessListener projectAccessListener) {
+                                ProjectAccessListener projectAccessListener,
+                                ProjectFinder projectFinder) {
         this.path = path;
         this.name = name;
         this.configurationsProvider = configurationsProvider;
@@ -89,30 +113,34 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         this.metaDataProvider = metaDataProvider;
         this.resolutionStrategy = resolutionStrategy;
         this.projectAccessListener = projectAccessListener;
+        this.projectFinder = projectFinder;
 
-        resolutionListenerBroadcast = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
-
-        RunnableMutationValidator veto = new RunnableMutationValidator(MutationType.CONTENT) {
-            @Override
-            public void validateMutation(MutationType type) {
-                DefaultConfiguration.this.validateMutation(type);
-            }
-        };
+        dependencyResolutionListeners = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
 
         DefaultDomainObjectSet<Dependency> ownDependencies = new DefaultDomainObjectSet<Dependency>(Dependency.class);
-        ownDependencies.beforeChange(veto);
+        ownDependencies.beforeChange(validateMutationType(this, MutationType.DEPENDENCIES));
 
         dependencies = new DefaultDependencySet(String.format("%s dependencies", getDisplayName()), ownDependencies);
         inheritedDependencies = CompositeDomainObjectSet.create(Dependency.class, ownDependencies);
         allDependencies = new DefaultDependencySet(String.format("%s all dependencies", getDisplayName()), inheritedDependencies);
 
         DefaultDomainObjectSet<PublishArtifact> ownArtifacts = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact.class);
-        ownArtifacts.beforeChange(veto);
+        ownArtifacts.beforeChange(validateMutationType(this, MutationType.ARTIFACTS));
+
         artifacts = new DefaultPublishArtifactSet(String.format("%s artifacts", getDisplayName()), ownArtifacts);
         inheritedArtifacts = CompositeDomainObjectSet.create(PublishArtifact.class, ownArtifacts);
         allArtifacts = new DefaultPublishArtifactSet(String.format("%s all artifacts", getDisplayName()), inheritedArtifacts);
 
-        resolutionStrategy.beforeChange(veto);
+        resolutionStrategy.setMutationValidator(this);
+    }
+
+    private static Runnable validateMutationType(final MutationValidator mutationValidator, final MutationType type) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                mutationValidator.validateMutation(type);
+            }
+        };
     }
 
     public String getName() {
@@ -120,11 +148,24 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public State getState() {
-        synchronized (lock) {
-            return state;
+        synchronized (resolutionLock) {
+            if (resolvedState == InternalState.RESULTS_RESOLVED || resolvedState == InternalState.TASK_DEPENDENCIES_RESOLVED) {
+                if (cachedResolverResults.hasError()) {
+                    return State.RESOLVED_WITH_FAILURES;
+                } else {
+                    return State.RESOLVED;
+                }
+            } else {
+                return State.UNRESOLVED;
+            }
         }
     }
 
+    @Override
+    public InternalState getResolvedState() {
+        return resolvedState;
+    }
+
     public ModuleInternal getModule() {
         return metaDataProvider.getModule();
     }
@@ -134,7 +175,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public Configuration setVisible(boolean visible) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         this.visibility = visible ? Visibility.PUBLIC : Visibility.PRIVATE;
         return this;
     }
@@ -144,10 +185,11 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public Configuration setExtendsFrom(Iterable<Configuration> extendsFrom) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         for (Configuration configuration : this.extendsFrom) {
             inheritedArtifacts.removeCollection(configuration.getAllArtifacts());
             inheritedDependencies.removeCollection(configuration.getAllDependencies());
+            ((ConfigurationInternal) configuration).removeMutationValidator(parentMutationValidator);
         }
         this.extendsFrom = new HashSet<Configuration>();
         for (Configuration configuration : extendsFrom) {
@@ -157,16 +199,18 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public Configuration extendsFrom(Configuration... extendsFrom) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         for (Configuration configuration : extendsFrom) {
             if (configuration.getHierarchy().contains(this)) {
                 throw new InvalidUserDataException(String.format(
-                        "Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
-                        configuration, configuration.getHierarchy()));
+                    "Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
+                    configuration, configuration.getHierarchy()));
+            }
+            if (this.extendsFrom.add(configuration)) {
+                inheritedArtifacts.addCollection(configuration.getAllArtifacts());
+                inheritedDependencies.addCollection(configuration.getAllDependencies());
+                ((ConfigurationInternal) configuration).addMutationValidator(parentMutationValidator);
             }
-            this.extendsFrom.add(configuration);
-            inheritedArtifacts.addCollection(configuration.getAllArtifacts());
-            inheritedDependencies.addCollection(configuration.getAllDependencies());
         }
         return this;
     }
@@ -176,7 +220,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public Configuration setTransitive(boolean transitive) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         this.transitive = transitive;
         return this;
     }
@@ -206,6 +250,28 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
     }
 
+    @Override
+    public Configuration defaultDependencies(Action<? super DependencySet> action) {
+        validateMutation(MutationType.DEPENDENCIES);
+        this.defaultDependencyActions.add(action);
+        return this;
+    }
+
+    @Override
+    public void triggerWhenEmptyActionsIfNecessary() {
+        if (!defaultDependencyActions.isEmpty()) {
+            for (Action<? super DependencySet> action : defaultDependencyActions) {
+                if (!dependencies.isEmpty()) {
+                    break;
+                }
+                action.execute(dependencies);
+            }
+        }
+        for (Configuration superConfig : extendsFrom) {
+            ((ConfigurationInternal) superConfig).triggerWhenEmptyActionsIfNecessary();
+        }
+    }
+
     public Set<Configuration> getAll() {
         return configurationsProvider.getAll();
     }
@@ -242,40 +308,119 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return new ConfigurationFileCollection(WrapUtil.toLinkedSet(dependencies));
     }
 
-    public void includedInResolveResult() {
-        includedInResult = true;
+    public void markAsObserved(InternalState requestedState) {
+        markThisObserved(requestedState);
+        markParentsObserved(requestedState);
+    }
+
+    private void markThisObserved(InternalState requestedState) {
+        synchronized (observationLock) {
+            if (observedState.compareTo(requestedState) < 0) {
+                observedState = requestedState;
+            }
+        }
+    }
+
+    private void markParentsObserved(InternalState requestedState) {
         for (Configuration configuration : extendsFrom) {
-            ((ConfigurationInternal) configuration).includedInResolveResult();
+            ((ConfigurationInternal) configuration).markAsObserved(requestedState);
         }
     }
 
     public ResolvedConfiguration getResolvedConfiguration() {
-        resolveNow();
+        resolveNow(InternalState.RESULTS_RESOLVED);
         return cachedResolverResults.getResolvedConfiguration();
     }
 
-    private void resolveNow() {
-        synchronized (lock) {
-            if (state == State.UNRESOLVED) {
-                DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
-                ResolvableDependencies incoming = getIncoming();
-                broadcast.beforeResolve(incoming);
-                cachedResolverResults = resolver.resolve(this);
-                for (Configuration configuration : extendsFrom) {
-                    ((ConfigurationInternal) configuration).includedInResolveResult();
-                }
-                if (cachedResolverResults.getResolvedConfiguration().hasError()) {
-                    state = State.RESOLVED_WITH_FAILURES;
-                } else {
-                    state = State.RESOLVED;
-                }
-                broadcast.afterResolve(incoming);
+    private void resolveNow(InternalState requestedState) {
+        synchronized (resolutionLock) {
+            if (requestedState == InternalState.TASK_DEPENDENCIES_RESOLVED || requestedState == InternalState.RESULTS_RESOLVED) {
+                resolveGraphIfRequired(requestedState);
+            }
+            if (requestedState == InternalState.RESULTS_RESOLVED) {
+                resolveArtifactsIfRequired();
+            }
+        }
+    }
+
+    private void resolveGraphIfRequired(final InternalState requestedState) {
+        if (resolvedState == InternalState.RESULTS_RESOLVED) {
+            if (dependenciesModified) {
+                DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Attempting to resolve %s that has been resolved previously. Changes made since the configuration was originally resolved are ignored", getDisplayName()));
+            }
+            return;
+        }
+        if (resolvedState == InternalState.TASK_DEPENDENCIES_RESOLVED) {
+            if (!dependenciesModified) {
+                return;
+            }
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Resolving %s again after modification", getDisplayName()));
+        }
+
+        ResolvableDependencies incoming = getIncoming();
+        performPreResolveActions(incoming);
+
+        resolver.resolve(this, cachedResolverResults);
+        dependenciesModified = false;
+        if (resolvedState != InternalState.RESULTS_RESOLVED) {
+            resolvedState = InternalState.TASK_DEPENDENCIES_RESOLVED;
+        }
+
+        // Mark all affected configurations as observed
+        markParentsObserved(requestedState);
+        markReferencedProjectConfigurationsObserved(requestedState);
+
+        dependencyResolutionListeners.getSource().afterResolve(incoming);
+    }
+
+    private void performPreResolveActions(ResolvableDependencies incoming) {
+        DependencyResolutionListener dependencyResolutionListener = dependencyResolutionListeners.getSource();
+        insideBeforeResolve = true;
+        try {
+            dependencyResolutionListener.beforeResolve(incoming);
+        } finally {
+            insideBeforeResolve = false;
+        }
+        triggerWhenEmptyActionsIfNecessary();
+    }
+
+    private void markReferencedProjectConfigurationsObserved(final InternalState requestedState) {
+        cachedResolverResults.eachResolvedProject(new Action<ResolvedProjectConfiguration>() {
+            @Override
+            public void execute(ResolvedProjectConfiguration projectResult) {
+                ProjectInternal project = projectFinder.getProject(projectResult.getId().getProjectPath());
+                ConfigurationInternal targetConfig = (ConfigurationInternal) project.getConfigurations().getByName(projectResult.getTargetConfiguration());
+                targetConfig.markAsObserved(requestedState);
             }
+        });
+    }
+
+    private void resolveArtifactsIfRequired() {
+        if (resolvedState == InternalState.RESULTS_RESOLVED) {
+            return;
         }
+        resolver.resolveArtifacts(this, cachedResolverResults);
+        resolvedState = InternalState.RESULTS_RESOLVED;
     }
 
     public TaskDependency getBuildDependencies() {
-        return allDependencies.getBuildDependencies();
+        if (resolutionStrategy.resolveGraphToDetermineTaskDependencies()) {
+            final DefaultTaskDependency taskDependency = new DefaultTaskDependency();
+            resolveNow(InternalState.TASK_DEPENDENCIES_RESOLVED);
+            cachedResolverResults.eachResolvedProject(new Action<ResolvedProjectConfiguration>() {
+                @Override
+                public void execute(ResolvedProjectConfiguration projectResult) {
+                    ProjectInternal project = projectFinder.getProject(projectResult.getId().getProjectPath());
+                    Configuration targetConfig = project.getConfigurations().getByName(projectResult.getTargetConfiguration());
+                    taskDependency.add(new SelfResolvingDependenciesWithoutProjectDependencies(targetConfig.getAllDependencies()));
+                    taskDependency.add(targetConfig.getAllArtifacts());
+                }
+            });
+            taskDependency.add(new SelfResolvingDependenciesWithoutProjectDependencies(allDependencies));
+            return taskDependency;
+        } else {
+            return allDependencies.getBuildDependencies();
+        }
     }
 
     /**
@@ -310,12 +455,12 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public void setExcludeRules(Set<ExcludeRule> excludeRules) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         this.excludeRules = excludeRules;
     }
 
     public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
-        validateMutation(MutationType.CONTENT);
+        validateMutation(MutationType.DEPENDENCIES);
         excludeRules.add(ExcludeRuleNotationConverter.parser().parseNotation(excludeRuleArgs)); //TODO SF try using ExcludeRuleContainer
         return this;
     }
@@ -336,7 +481,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return resolvableDependencies;
     }
 
-    public Configuration copy() {
+    public ConfigurationInternal copy() {
         return createCopy(getDependencies(), false);
     }
 
@@ -355,7 +500,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     private DefaultConfiguration createCopy(Set<Dependency> dependencies, boolean recursive) {
         DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
         DefaultConfiguration copiedConfiguration = new DefaultConfiguration(path + "Copy", name + "Copy",
-                configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy.copy(), projectAccessListener);
+            configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy.copy(), projectAccessListener, projectFinder);
         configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
         // state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
         // copying extendsFrom could mess up dependencies when copy was re-resolved
@@ -364,6 +509,8 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         copiedConfiguration.transitive = transitive;
         copiedConfiguration.description = description;
 
+        copiedConfiguration.defaultDependencyActions.addAll(defaultDependencyActions);
+
         copiedConfiguration.getArtifacts().addAll(getAllArtifacts());
 
         // todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
@@ -396,10 +543,6 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
     }
 
-    public DependencyResolutionListener getDependencyResolutionBroadcast() {
-        return resolutionListenerBroadcast.getSource();
-    }
-
     public ResolutionStrategyInternal getResolutionStrategy() {
         return resolutionStrategy;
     }
@@ -413,18 +556,82 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return this;
     }
 
-    private void validateMutation(MutationType type) {
-        boolean userAlreadyNagged = false;
-        if (getState() != State.UNRESOLVED) {
-            if (type == MutationType.CONTENT) {
-                throw new InvalidUserDataException(String.format("Cannot change %s after it has been resolved.", getDisplayName()));
+    @Override
+    public void addMutationValidator(MutationValidator validator) {
+        childMutationValidators.add(validator);
+    }
+
+    @Override
+    public void removeMutationValidator(MutationValidator validator) {
+        childMutationValidators.remove(validator);
+    }
+
+    private void validateParentMutation(MutationType type) {
+        // Strategy changes in a parent configuration do not affect this configuration, or any of its children, in any way
+        if (type == MutationType.STRATEGY) {
+            return;
+        }
+
+        if (resolvedState == InternalState.RESULTS_RESOLVED) {
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Changed %s of parent of %s after it has been resolved", type, getDisplayName()));
+        } else if (resolvedState == InternalState.TASK_DEPENDENCIES_RESOLVED) {
+            if (type == MutationType.DEPENDENCIES) {
+                DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Changed %s of parent of %s after task dependencies have been resolved", type, getDisplayName()));
+            }
+        }
+
+        markAsModifiedAndNotifyChildren(type);
+    }
+
+    public void validateMutation(MutationType type) {
+        if (resolvedState == InternalState.RESULTS_RESOLVED) {
+            // The public result for the configuration has been calculated.
+            // It is an error to change anything that would change the dependencies or artifacts,
+            // and deprecated to change the resolution strategy.
+            if (type != MutationType.STRATEGY) {
+                throw new InvalidUserDataException(String.format("Cannot change %s of %s after it has been resolved.", type, getDisplayName()));
             } else {
-                userAlreadyNagged = true;
-                DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Attempting to change %s after it has been resolved", getDisplayName()));
+                DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Changed %s of %s after it has been resolved", type, getDisplayName()));
+            }
+        } else if (resolvedState == InternalState.TASK_DEPENDENCIES_RESOLVED) {
+            // The task dependencies for the configuration have been calculated using Configuration.getBuildDependencies().
+            // It is deprecated for build logic to change anything about the configuration.
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Changed %s of %s after task dependencies have been resolved", type, getDisplayName()));
+        } else if (observedState == InternalState.TASK_DEPENDENCIES_RESOLVED || observedState == InternalState.RESULTS_RESOLVED) {
+            // The configuration has been used in a resolution, and it is deprecated for build logic to change any dependencies,
+            // exclude rules or parent configurations (values that will affect the resolved graph).
+            if (type != MutationType.STRATEGY) {
+                String extraMessage = insideBeforeResolve ? " Use 'defaultDependencies' instead of 'beforeResolve' to specify default dependencies for a configuration." : "";
+                DeprecationLogger.nagUserWith(String.format("Changed %s of %s after it has been included in dependency resolution. This behaviour %s.%s", type, getDisplayName(), DeprecationLogger.getDeprecationMessage(), extraMessage));
             }
         }
-        if (!userAlreadyNagged && includedInResult) {
-            DeprecationLogger.nagUserOfDeprecatedBehaviour(String.format("Attempting to change %s after it has been included in dependency resolution", getDisplayName()));
+
+        markAsModifiedAndNotifyChildren(type);
+    }
+
+    private void markAsModifiedAndNotifyChildren(MutationType type) {
+        // Notify child configurations
+        for (MutationValidator validator : childMutationValidators) {
+            validator.validateMutation(type);
+        }
+        if (type != MutationType.STRATEGY) {
+            dependenciesModified = true;
+        }
+    }
+
+    private static class SelfResolvingDependenciesWithoutProjectDependencies extends AbstractTaskDependency {
+        private final DependencySet dependencies;
+
+        private SelfResolvingDependenciesWithoutProjectDependencies(DependencySet dependencies) {
+            this.dependencies = dependencies;
+        }
+
+        public void resolve(TaskDependencyResolveContext context) {
+            for (SelfResolvingDependency dependency : dependencies.withType(SelfResolvingDependency.class)) {
+                if (!(dependency instanceof ProjectDependency)) {
+                    context.add(dependency);
+                }
+            }
         }
     }
 
@@ -461,7 +668,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
 
         public Set<File> getFiles() {
-            synchronized (lock) {
+            synchronized (resolutionLock) {
                 ResolvedConfiguration resolvedConfiguration = getResolvedConfiguration();
                 if (getState() == State.RESOLVED_WITH_FAILURES) {
                     resolvedConfiguration.rethrowFailure();
@@ -471,6 +678,16 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
     }
 
+    @Override
+    public void registerWatchPoints(FileSystemSubset.Builder builder) {
+        for (Dependency dependency : allDependencies) {
+            if (dependency instanceof FileCollectionDependency) {
+                ((FileCollectionDependency) dependency).registerWatchPoints(builder);
+            }
+        }
+        super.registerWatchPoints(builder);
+    }
+
     /**
      * Print a formatted representation of a Configuration
      */
@@ -545,23 +762,23 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
 
         public void beforeResolve(Action<? super ResolvableDependencies> action) {
-            resolutionListenerBroadcast.add("beforeResolve", action);
+            dependencyResolutionListeners.add("beforeResolve", action);
         }
 
         public void beforeResolve(Closure action) {
-            resolutionListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("beforeResolve", action));
+            dependencyResolutionListeners.add(new ClosureBackedMethodInvocationDispatch("beforeResolve", action));
         }
 
         public void afterResolve(Action<? super ResolvableDependencies> action) {
-            resolutionListenerBroadcast.add("afterResolve", action);
+            dependencyResolutionListeners.add("afterResolve", action);
         }
 
         public void afterResolve(Closure action) {
-            resolutionListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("afterResolve", action));
+            dependencyResolutionListeners.add(new ClosureBackedMethodInvocationDispatch("afterResolve", action));
         }
 
         public ResolutionResult getResolutionResult() {
-            DefaultConfiguration.this.resolveNow();
+            DefaultConfiguration.this.resolveNow(InternalState.RESULTS_RESOLVED);
             return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
         }
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
index 369605c..2717187 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -23,6 +23,7 @@ import org.gradle.api.artifacts.UnknownConfigurationException;
 import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
 import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.artifacts.ConfigurationResolver;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
 import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.reflect.Instantiator;
@@ -41,12 +42,13 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
     private final ListenerManager listenerManager;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
     private final ProjectAccessListener projectAccessListener;
+    private final ProjectFinder projectFinder;
 
     private int detachedConfigurationDefaultNameCounter = 1;
 
     public DefaultConfigurationContainer(ConfigurationResolver resolver,
                                          Instantiator instantiator, DomainObjectContext context, ListenerManager listenerManager,
-                                         DependencyMetaDataProvider dependencyMetaDataProvider, ProjectAccessListener projectAccessListener) {
+                                         DependencyMetaDataProvider dependencyMetaDataProvider, ProjectAccessListener projectAccessListener, ProjectFinder projectFinder) {
         super(Configuration.class, instantiator, new Configuration.Namer());
         this.resolver = resolver;
         this.instantiator = instantiator;
@@ -54,12 +56,13 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
         this.listenerManager = listenerManager;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
         this.projectAccessListener = projectAccessListener;
+        this.projectFinder = projectFinder;
     }
 
     @Override
     protected Configuration doCreate(String name) {
         return instantiator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name), name, this, resolver,
-                listenerManager, dependencyMetaDataProvider, instantiator.newInstance(DefaultResolutionStrategy.class), projectAccessListener);
+                listenerManager, dependencyMetaDataProvider, instantiator.newInstance(DefaultResolutionStrategy.class), projectAccessListener, projectFinder);
     }
 
     public Set<Configuration> getAll() {
@@ -86,7 +89,7 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
         DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
         DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
                 name, name, detachedConfigurationsProvider, resolver,
-                listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy(), projectAccessListener);
+                listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy(), projectAccessListener, projectFinder);
         DomainObjectSet<Dependency> detachedDependencies = detachedConfiguration.getDependencies();
         for (Dependency dependency : dependencies) {
             detachedDependencies.add(dependency.copy());
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/MutationValidator.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/MutationValidator.java
index 4a9bfda..70a61be 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/MutationValidator.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/MutationValidator.java
@@ -27,9 +27,19 @@ public interface MutationValidator {
         STRATEGY,
 
         /**
-         * The mutation of the content of the configuration, i.e. dependencies, artifacts, extended configurations etc.
+         * The mutation of anything that will affect the resolved dependency graph of this configuration.
          */
-        CONTENT
+        DEPENDENCIES,
+
+        /**
+         * The mutation of the artifacts of the configuration.
+         */
+        ARTIFACTS;
+
+        @Override
+        public String toString() {
+            return name().toLowerCase();
+        }
     }
 
     /**
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
index 944fc1a..bf26449 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
@@ -20,10 +20,9 @@ import org.gradle.api.artifacts.ConflictResolution;
 import org.gradle.api.artifacts.DependencySubstitution;
 import org.gradle.api.artifacts.ResolutionStrategy;
 import org.gradle.api.artifacts.cache.ResolutionRules;
-import org.gradle.api.artifacts.component.ComponentSelector;
 import org.gradle.api.internal.artifacts.ComponentSelectionRulesInternal;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
-import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DependencySubstitutionsInternal;
+import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DependencySubstitutionsInternal;
 
 public interface ResolutionStrategyInternal extends ResolutionStrategy {
 
@@ -50,16 +49,27 @@ public interface ResolutionStrategyInternal extends ResolutionStrategy {
     /**
      * @return the dependency substitution rule (may aggregate multiple rules)
      */
-    Action<DependencySubstitution<ComponentSelector>> getDependencySubstitutionRule();
+    Action<DependencySubstitution> getDependencySubstitutionRule();
 
     /**
-     * @return the version selection rules object
+     * Used by tests to validate behaviour of the 'task graph modified' state
      */
-    ComponentSelectionRulesInternal getComponentSelection();
+    void assumeFluidDependencies();
+
+    /**
+     * Should the configuration be fully resolved to determine the task dependencies?
+     * If not, we do a shallow 'resolve' of SelfResolvingDependencies only.
+     */
+    boolean resolveGraphToDetermineTaskDependencies();
 
     DependencySubstitutionsInternal getDependencySubstitution();
 
     /**
+     * @return the version selection rules object
+     */
+    ComponentSelectionRulesInternal getComponentSelection();
+
+    /**
      * @return copy of this resolution strategy. See the contract of {@link org.gradle.api.artifacts.Configuration#copy()}.
      */
     ResolutionStrategyInternal copy();
@@ -67,5 +77,5 @@ public interface ResolutionStrategyInternal extends ResolutionStrategy {
     /**
      * Sets the validator to invoke before mutation. Any exception thrown by the action will veto the mutation.
      */
-    void beforeChange(MutationValidator action);
+    void setMutationValidator(MutationValidator action);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/ComponentModuleMetadataContainer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/ComponentModuleMetadataContainer.java
index 8f7563c..680d231 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/ComponentModuleMetadataContainer.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/ComponentModuleMetadataContainer.java
@@ -20,7 +20,7 @@ import com.google.common.base.Joiner;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ComponentModuleMetadataDetails;
 import org.gradle.api.artifacts.ModuleIdentifier;
-import org.gradle.api.internal.notations.ModuleIdentiferNotationConverter;
+import org.gradle.api.internal.notations.ModuleIdentifierNotationConverter;
 import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.internal.typeconversion.NotationParserBuilder;
 
@@ -87,7 +87,7 @@ public class ComponentModuleMetadataContainer implements ModuleReplacementsData
     private static NotationParser<Object, ModuleIdentifier> parser() {
         return NotationParserBuilder
                 .toType(ModuleIdentifier.class)
-                .converter(new ModuleIdentiferNotationConverter())
+                .converter(new ModuleIdentifierNotationConverter())
                 .toComposite();
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java
index c52742b..4549d4c 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataHandler.java
@@ -27,7 +27,7 @@ import org.gradle.api.artifacts.ivy.IvyModuleDescriptor;
 import org.gradle.api.internal.artifacts.ComponentMetadataProcessor;
 import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyModuleDescriptor;
 import org.gradle.api.internal.artifacts.repositories.resolver.ComponentMetadataDetailsAdapter;
-import org.gradle.api.internal.notations.ModuleIdentiferNotationConverter;
+import org.gradle.api.internal.notations.ModuleIdentifierNotationConverter;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.internal.component.external.model.IvyModuleResolveMetaData;
@@ -71,7 +71,7 @@ public class DefaultComponentMetadataHandler implements ComponentMetadataHandler
     private static NotationParser<Object, ModuleIdentifier> createModuleIdentifierNotationParser() {
         return NotationParserBuilder
                 .toType(ModuleIdentifier.class)
-                .converter(new ModuleIdentiferNotationConverter())
+                .converter(new ModuleIdentifierNotationConverter())
                 .toComposite();
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/AbstractDependencySubstitution.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/AbstractDependencySubstitution.java
deleted file mode 100644
index 2072fcb..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/AbstractDependencySubstitution.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.artifacts.component.ComponentSelector;
-import org.gradle.api.artifacts.result.ComponentSelectionReason;
-import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
-import org.gradle.api.internal.artifacts.dsl.ComponentSelectorParsers;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
-
-public abstract class AbstractDependencySubstitution<T extends ComponentSelector> implements DependencySubstitutionInternal<T> {
-    private final T requested;
-    private final ModuleVersionSelector oldRequested;
-    private ComponentSelectionReason selectionReason;
-    private ComponentSelector target;
-
-    public AbstractDependencySubstitution(T requested, ModuleVersionSelector oldRequested) {
-        this.requested = requested;
-        this.target = requested;
-        this.oldRequested = oldRequested;
-    }
-
-    @Override
-    public T getRequested() {
-        return requested;
-    }
-
-    @Override
-    public ModuleVersionSelector getOldRequested() {
-        return oldRequested;
-    }
-
-    @Override
-    public void useTarget(Object notation) {
-        useTarget(notation, VersionSelectionReasons.SELECTED_BY_RULE);
-    }
-
-    @Override
-    public void useTarget(Object notation, ComponentSelectionReason selectionReason) {
-        this.target = ComponentSelectorParsers.parser().parseNotation(notation);
-        this.selectionReason = selectionReason;
-    }
-
-    @Override
-    public ComponentSelectionReason getSelectionReason() {
-        return selectionReason;
-    }
-
-    @Override
-    public ComponentSelector getTarget() {
-        return target;
-    }
-
-    @Override
-    public boolean isUpdated() {
-        return selectionReason != null;
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
index d45821c..581f2ac 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
@@ -15,11 +15,11 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.gradle.api.artifacts.ResolveContext;
 import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.GlobalDependencyResolutionRules;
 import org.gradle.api.internal.artifacts.ResolverResults;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 
 import java.util.List;
@@ -33,14 +33,25 @@ public class CacheLockingArtifactDependencyResolver implements ArtifactDependenc
         this.resolver = resolver;
     }
 
-    public void resolve(final ConfigurationInternal configuration,
+    public void resolve(final ResolveContext resolveContext,
                                    final List<? extends ResolutionAwareRepository> repositories,
                                    final GlobalDependencyResolutionRules metadataHandler,
                                    final ResolverResults results) throws ResolveException {
-        lockingManager.useCache(String.format("resolve %s", configuration), new Runnable() {
+        lockingManager.useCache(String.format("resolve %s", resolveContext), new Runnable() {
             public void run() {
-                resolver.resolve(configuration, repositories, metadataHandler, results);
+                resolver.resolve(resolveContext, repositories, metadataHandler, results);
             }
         });
     }
-}
\ No newline at end of file
+
+    public void resolveArtifacts(final ResolveContext resolveContext,
+                                   final List<? extends ResolutionAwareRepository> repositories,
+                                   final GlobalDependencyResolutionRules metadataHandler,
+                                   final ResolverResults results) throws ResolveException {
+        lockingManager.useCache(String.format("resolve %s", resolveContext), new Runnable() {
+            public void run() {
+                resolver.resolveArtifacts(resolveContext, repositories, metadataHandler, results);
+            }
+        });
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
index 1136fcf..168916d 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
@@ -40,10 +40,13 @@ public class DefaultConfigurationResolver implements ConfigurationResolver {
         this.metadataHandler = metadataHandler;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException {
+    public void resolve(ConfigurationInternal configuration, ResolverResults results) throws ResolveException {
         List<ResolutionAwareRepository> resolutionAwareRepositories = CollectionUtils.collect(repositories, Transformers.cast(ResolutionAwareRepository.class));
-        ResolverResults results = new ResolverResults();
         resolver.resolve(configuration, resolutionAwareRepositories, metadataHandler, results);
-        return results;
+    }
+
+    public void resolveArtifacts(ConfigurationInternal configuration, ResolverResults results) throws ResolveException {
+        List<ResolutionAwareRepository> resolutionAwareRepositories = CollectionUtils.collect(repositories, Transformers.cast(ResolutionAwareRepository.class));
+        resolver.resolveArtifacts(configuration, resolutionAwareRepositories, metadataHandler, results);
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
deleted file mode 100644
index c12720a..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.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.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.component.ModuleComponentSelector;
-import org.gradle.api.artifacts.result.ComponentSelectionReason;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
-import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
-import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
-import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
-
-public class DefaultDependencyResolveDetails implements DependencyResolveDetailsInternal {
-
-    private final DependencySubstitutionInternal<?> delegate;
-    private ModuleVersionSelector target;
-
-    public DefaultDependencyResolveDetails(DependencySubstitutionInternal<?> delegate) {
-        this.delegate = delegate;
-        target = determineTarget(delegate);
-    }
-
-    private static ModuleVersionSelector determineTarget(DependencySubstitutionInternal<?> delegate) {
-        // Temporary logic until we add DependencySubstitution back in
-        if (delegate.getTarget() instanceof ModuleComponentSelector) {
-            ModuleComponentSelector moduleComponentSelector = (ModuleComponentSelector) delegate.getTarget();
-            return DefaultModuleVersionSelector.newSelector(moduleComponentSelector.getGroup(), moduleComponentSelector.getModule(), moduleComponentSelector.getVersion());
-        }
-        // If the target is a project component, it must be unmodified from the requested
-        return delegate.getOldRequested();
-    }
-
-    @Override
-    public ModuleVersionSelector getRequested() {
-        return delegate.getOldRequested();
-    }
-
-    @Override
-    public void useVersion(String version) {
-        useVersion(version, VersionSelectionReasons.SELECTED_BY_RULE);
-    }
-
-    @Override
-    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.");
-        }
-
-        if (!version.equals(target.getVersion())) {
-            target = DefaultModuleVersionSelector.newSelector(target.getGroup(), target.getName(), version);
-            delegate.useTarget(DefaultModuleComponentSelector.newSelector(target), selectionReason);
-        } else {
-            // Still 'updated' with reason when version remains the same.
-            delegate.useTarget(delegate.getTarget(), selectionReason);
-        }
-    }
-
-    @Override
-    public void useTarget(Object notation) {
-        target = ModuleVersionSelectorParsers.parser().parseNotation(notation);
-        delegate.useTarget(DefaultModuleComponentSelector.newSelector(target), VersionSelectionReasons.SELECTED_BY_RULE);
-    }
-
-    @Override
-    public ComponentSelectionReason getSelectionReason() {
-        return delegate.getSelectionReason();
-    }
-
-    @Override
-    public ModuleVersionSelector getTarget() {
-        return target;
-    }
-
-    @Override
-    public boolean isUpdated() {
-        return delegate.isUpdated();
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
index db57012..7864a1a 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
@@ -37,7 +37,7 @@ public class DefaultIvyDependencyPublisher implements IvyDependencyPublisher {
                         IvyModulePublishMetaData publishMetaData) {
         try {
             // Make a copy of the publication and filter missing artifacts
-            DefaultIvyModulePublishMetaData publication = new DefaultIvyModulePublishMetaData(publishMetaData.getId());
+            DefaultIvyModulePublishMetaData publication = new DefaultIvyModulePublishMetaData(publishMetaData.getId(), publishMetaData.getModuleDescriptor());
             for (IvyModuleArtifactPublishMetaData artifact: publishMetaData.getArtifacts()) {
                 addPublishedArtifact(artifact, publication);
             }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
index 5d5da17..3d7b0dd 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
@@ -16,13 +16,15 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.*;
-import org.gradle.internal.resolve.ArtifactResolveException;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedConfigurationResults;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedArtifactResults;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedGraphResults;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults;
 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.internal.resolve.ArtifactResolveException;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
@@ -31,39 +33,48 @@ import java.util.*;
 public class DefaultLenientConfiguration implements LenientConfiguration {
     private CacheLockingManager cacheLockingManager;
     private final Configuration configuration;
-    private ResolvedConfigurationResults results;
+    private final ResolvedGraphResults graphResults;
+    private final ResolvedArtifactResults artifactResults;
+    private final Factory<TransientConfigurationResults> transientConfigurationResultsFactory;
 
-    public DefaultLenientConfiguration(Configuration configuration, ResolvedConfigurationResults results, CacheLockingManager cacheLockingManager) {
+    public DefaultLenientConfiguration(Configuration configuration, CacheLockingManager cacheLockingManager, ResolvedGraphResults graphResults, ResolvedArtifactResults artifactResults,
+                                       Factory<TransientConfigurationResults> transientConfigurationResultsLoader) {
         this.configuration = configuration;
-        this.results = results;
         this.cacheLockingManager = cacheLockingManager;
+        this.graphResults = graphResults;
+        this.artifactResults = artifactResults;
+        this.transientConfigurationResultsFactory = transientConfigurationResultsLoader;
     }
 
     public boolean hasError() {
-        return results.hasError();
+        return graphResults.hasError();
+    }
+
+    public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
+        return graphResults.getUnresolvedDependencies();
     }
 
     public void rethrowFailure() throws ResolveException {
         if (hasError()) {
             List<Throwable> failures = new ArrayList<Throwable>();
-            for (UnresolvedDependency unresolvedDependency : results.getUnresolvedDependencies()) {
+            for (UnresolvedDependency unresolvedDependency : graphResults.getUnresolvedDependencies()) {
                 failures.add(unresolvedDependency.getProblem());
             }
-            throw new ResolveException(configuration, failures);
+            throw new ResolveException((ResolveContext) configuration, failures);
         }
     }
 
-    public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
-        return results.getUnresolvedDependencies();
+    public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+        return artifactResults.getArtifacts();
     }
 
-    public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
-        return results.getArtifacts();
+    private TransientConfigurationResults loadTransientGraphResults() {
+        return transientConfigurationResultsFactory.create();
     }
 
     public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) {
         Set<ResolvedDependency> matches = new LinkedHashSet<ResolvedDependency>();
-        for (Map.Entry<ModuleDependency, ResolvedDependency> entry : results.more().getFirstLevelDependencies().entrySet()) {
+        for (Map.Entry<ModuleDependency, ResolvedDependency> entry : loadTransientGraphResults().getFirstLevelDependencies().entrySet()) {
             if (dependencySpec.isSatisfiedBy(entry.getKey())) {
                 matches.add(entry.getValue());
             }
@@ -128,7 +139,7 @@ public class DefaultLenientConfiguration implements LenientConfiguration {
         //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();
+            return artifactResults.getArtifacts();
         }
 
         CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
@@ -139,7 +150,7 @@ public class DefaultLenientConfiguration implements LenientConfiguration {
         Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
 
         for (ResolvedDependency resolvedDependency : firstLevelModuleDependencies) {
-            artifacts.addAll(resolvedDependency.getParentArtifacts(results.more().getRoot()));
+            artifacts.addAll(resolvedDependency.getParentArtifacts(loadTransientGraphResults().getRoot()));
             walker.add(resolvedDependency);
         }
 
@@ -152,7 +163,7 @@ public class DefaultLenientConfiguration implements LenientConfiguration {
     }
 
     public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-        return results.more().getRoot().getChildren();
+        return loadTransientGraphResults().getRoot().getChildren();
     }
 
     private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
@@ -166,4 +177,4 @@ public class DefaultLenientConfiguration implements LenientConfiguration {
             values.addAll(to.getParentArtifacts(from));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitution.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitution.java
deleted file mode 100644
index 46fb15c..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitution.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.artifacts.component.ModuleComponentSelector;
-import org.gradle.api.artifacts.result.ComponentSelectionReason;
-import org.gradle.api.internal.artifacts.ModuleDependencySubstitutionInternal;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
-
-public class DefaultModuleDependencySubstitution extends AbstractDependencySubstitution<ModuleComponentSelector> implements ModuleDependencySubstitutionInternal {
-
-    public DefaultModuleDependencySubstitution(ModuleComponentSelector requested, ModuleVersionSelector oldRequested) {
-        super(requested, oldRequested);
-    }
-
-    @Override
-    public void useVersion(String version) {
-        useVersion(version, VersionSelectionReasons.SELECTED_BY_RULE);
-    }
-
-    @Override
-    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.");
-        }
-        ModuleComponentSelector moduleTarget = getRequested();
-        if (!version.equals(moduleTarget.getVersion())) {
-            useTarget(DefaultModuleComponentSelector.newSelector(moduleTarget.getGroup(), moduleTarget.getModule(), version), selectionReason);
-        } else {
-            useTarget(moduleTarget, selectionReason);
-        }
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitution.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitution.java
deleted file mode 100644
index 6d0f78a..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitution.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.artifacts.ProjectDependencySubstitution;
-import org.gradle.api.artifacts.component.ProjectComponentSelector;
-
-public class DefaultProjectDependencySubstitution extends AbstractDependencySubstitution<ProjectComponentSelector> implements ProjectDependencySubstitution {
-
-    public DefaultProjectDependencySubstitution(ProjectComponentSelector requested, ModuleVersionSelector oldRequested) {
-        super(requested, oldRequested);
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolver.java
deleted file mode 100644
index bd55870..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolver.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.Action;
-import org.gradle.api.artifacts.DependencySubstitution;
-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.DependencySubstitutionInternal;
-import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.resolve.ModuleVersionResolveException;
-import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
-
-public class DependencySubstitutionResolver implements DependencyToComponentIdResolver {
-    private final DependencyToComponentIdResolver resolver;
-    private final Action<DependencySubstitution<ComponentSelector>> rule;
-
-    public DependencySubstitutionResolver(DependencyToComponentIdResolver resolver, Action<DependencySubstitution<ComponentSelector>> rule) {
-        this.resolver = resolver;
-        this.rule = rule;
-    }
-
-    public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
-        ComponentSelector selector = dependency.getSelector();
-        DependencySubstitutionInternal details;
-        if (selector instanceof ModuleComponentSelector) {
-            details = new DefaultModuleDependencySubstitution((ModuleComponentSelector) selector, dependency.getRequested());
-        } else if (selector instanceof ProjectComponentSelector) {
-            details = new DefaultProjectDependencySubstitution((ProjectComponentSelector) selector, dependency.getRequested());
-        } else {
-            throw new IllegalStateException("Unknown type of component selector: " + selector);
-        }
-        try {
-            rule.execute(details);
-        } catch (Throwable e) {
-            result.failed(new ModuleVersionResolveException(selector, e));
-            return;
-        }
-        if (details.isUpdated()) {
-            resolver.resolve(dependency.withTarget(details.getTarget()), result);
-            result.setSelectionReason(details.getSelectionReason());
-            return;
-        }
-        resolver.resolve(dependency, result);
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
index 5f9a2c4..926ff2b 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
@@ -24,7 +24,6 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.GlobalDependencyResolutionRules;
 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.api.specs.Spec;
 
@@ -39,22 +38,34 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         this.dependencyResolver = dependencyResolver;
     }
 
-    public void resolve(ConfigurationInternal configuration,
+    public void resolve(ResolveContext resolveContext,
                         List<? extends ResolutionAwareRepository> repositories,
                         GlobalDependencyResolutionRules metadataHandler,
                         ResolverResults results) throws ResolveException {
         try {
-            dependencyResolver.resolve(configuration, repositories, metadataHandler, results);
+            dependencyResolver.resolve(resolveContext, repositories, metadataHandler, results);
         } catch (final Throwable e) {
-            results.failed(new BrokenResolvedConfiguration(e, configuration), wrapException(e, configuration));
+            results.failed(wrapException(e, resolveContext));
+            results.withResolvedConfiguration(new BrokenResolvedConfiguration(e, resolveContext));
             return;
         }
-        ResolvedConfiguration wrappedConfiguration = new ErrorHandlingResolvedConfiguration(results.getResolvedConfiguration(), configuration);
-        ResolutionResult wrappedResult = new ErrorHandlingResolutionResult(results.getResolutionResult(), configuration);
-        results.resolved(wrappedConfiguration, wrappedResult, results.getResolvedProjectConfigurationResults());
+        ResolutionResult wrappedResult = new ErrorHandlingResolutionResult(results.getResolutionResult(), resolveContext);
+        results.resolved(wrappedResult, results.getResolvedProjectConfigurationResults());
     }
 
-    private static ResolveException wrapException(Throwable e, Configuration configuration) {
+    public void resolveArtifacts(ResolveContext resolveContext, List<? extends ResolutionAwareRepository> repositories, GlobalDependencyResolutionRules metadataHandler, ResolverResults results) throws ResolveException {
+        try {
+            dependencyResolver.resolveArtifacts(resolveContext, repositories, metadataHandler, results);
+        } catch (ResolveException e) {
+            results.withResolvedConfiguration(new BrokenResolvedConfiguration(e, resolveContext));
+            return;
+        }
+
+        ResolvedConfiguration wrappedConfiguration = new ErrorHandlingResolvedConfiguration(results.getResolvedConfiguration(), resolveContext);
+        results.withResolvedConfiguration(wrappedConfiguration);
+    }
+
+    private static ResolveException wrapException(Throwable e, ResolveContext configuration) {
         if (e instanceof ResolveException) {
             return (ResolveException) e;
         }
@@ -63,18 +74,18 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
 
     private static class ErrorHandlingLenientConfiguration implements LenientConfiguration {
         private final LenientConfiguration lenientConfiguration;
-        private final Configuration configuration;
+        private final ResolveContext resolveContext;
 
-        private ErrorHandlingLenientConfiguration(LenientConfiguration lenientConfiguration, Configuration configuration) {
+        private ErrorHandlingLenientConfiguration(LenientConfiguration lenientConfiguration, ResolveContext resolveContext) {
             this.lenientConfiguration = lenientConfiguration;
-            this.configuration = configuration;
+            this.resolveContext = resolveContext;
         }
 
         public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) {
             try {
                 return lenientConfiguration.getArtifacts(dependencySpec);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -82,7 +93,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return lenientConfiguration.getFirstLevelModuleDependencies(dependencySpec);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -90,7 +101,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return lenientConfiguration.getUnresolvedModuleDependencies();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -98,25 +109,25 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return lenientConfiguration.getFiles(dependencySpec);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
     }
 
     private static class ErrorHandlingResolutionResult implements ResolutionResult {
         private final ResolutionResult resolutionResult;
-        private final Configuration configuration;
+        private final ResolveContext resolveContext;
 
-        public ErrorHandlingResolutionResult(ResolutionResult resolutionResult, Configuration configuration) {
+        public ErrorHandlingResolutionResult(ResolutionResult resolutionResult, ResolveContext configuration) {
             this.resolutionResult = resolutionResult;
-            this.configuration = configuration;
+            this.resolveContext = configuration;
         }
 
         public ResolvedComponentResult getRoot() {
             try {
                 return resolutionResult.getRoot();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -128,7 +139,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolutionResult.getAllDependencies();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -140,7 +151,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolutionResult.getAllComponents();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -152,15 +163,15 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             resolutionResult.allComponents(closure);
         }
     }
-    
+
     private static class ErrorHandlingResolvedConfiguration implements ResolvedConfiguration {
         private final ResolvedConfiguration resolvedConfiguration;
-        private final Configuration configuration;
+        private final ResolveContext resolveContext;
 
         public ErrorHandlingResolvedConfiguration(ResolvedConfiguration resolvedConfiguration,
-                                                  Configuration configuration) {
+                                                  ResolveContext resolveContext) {
             this.resolvedConfiguration = resolvedConfiguration;
-            this.configuration = configuration;
+            this.resolveContext = resolveContext;
         }
 
         public boolean hasError() {
@@ -169,9 +180,9 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
 
         public LenientConfiguration getLenientConfiguration() {
             try {
-                return new ErrorHandlingLenientConfiguration(resolvedConfiguration.getLenientConfiguration(), configuration);
+                return new ErrorHandlingLenientConfiguration(resolvedConfiguration.getLenientConfiguration(), resolveContext);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -179,7 +190,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 resolvedConfiguration.rethrowFailure();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -187,7 +198,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolvedConfiguration.getFiles(dependencySpec);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -195,7 +206,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolvedConfiguration.getFirstLevelModuleDependencies();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -203,7 +214,7 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
 
@@ -211,18 +222,18 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
             try {
                 return resolvedConfiguration.getResolvedArtifacts();
             } catch (Throwable e) {
-                throw wrapException(e, configuration);
+                throw wrapException(e, resolveContext);
             }
         }
     }
 
     private static class BrokenResolvedConfiguration implements ResolvedConfiguration {
         private final Throwable e;
-        private final Configuration configuration;
+        private final ResolveContext resolveContext;
 
-        public BrokenResolvedConfiguration(Throwable e, Configuration configuration) {
+        public BrokenResolvedConfiguration(Throwable e, ResolveContext resolveContext) {
             this.e = e;
-            this.configuration = configuration;
+            this.resolveContext = resolveContext;
         }
 
         public boolean hasError() {
@@ -230,27 +241,27 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         }
 
         public LenientConfiguration getLenientConfiguration() {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
 
         public void rethrowFailure() throws ResolveException {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
 
         public Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
 
         public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
 
         public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
 
         public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
-            throw wrapException(e, configuration);
+            throw wrapException(e, resolveContext);
         }
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
index f537a80..c89f767 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
@@ -18,16 +18,17 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.apache.ivy.Ivy;
 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.PublishException;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
 import org.gradle.api.internal.artifacts.ModuleInternal;
 import org.gradle.api.internal.artifacts.ModuleVersionPublisher;
-import org.gradle.internal.component.external.model.BuildableIvyModulePublishMetaData;
-import org.gradle.internal.component.local.model.MutableLocalComponentMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ComponentConverterSource;
 import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+import org.gradle.internal.component.external.model.BuildableIvyModulePublishMetaData;
+import org.gradle.internal.component.external.model.IvyModulePublishMetaData;
+import org.gradle.internal.component.local.model.LocalComponentMetaData;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -56,17 +57,17 @@ public class IvyBackedArtifactPublisher implements ArtifactPublisher {
                 Set<Configuration> allConfigurations = configuration.getAll();
                 Set<Configuration> configurationsToPublish = configuration.getHierarchy();
 
-                MutableLocalComponentMetaData componentMetaData = publishLocalComponentFactory.convert(allConfigurations, module);
+                LocalComponentMetaData allConfigurationsComponentMetaData = publishLocalComponentFactory.convert(new ComponentConverterSource(allConfigurations, module));
                 if (descriptor != null) {
-                    ModuleDescriptor moduleDescriptor = componentMetaData.getModuleDescriptor();
-                    ivyModuleDescriptorWriter.write(moduleDescriptor, descriptor);
+                    IvyModulePublishMetaData publishMetaData = allConfigurationsComponentMetaData.toPublishMetaData();
+                    ivyModuleDescriptorWriter.write(publishMetaData.getModuleDescriptor(), publishMetaData.getArtifacts(), 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);
+                LocalComponentMetaData componentMetaData = publishLocalComponentFactory.convert(new ComponentConverterSource(configurationsToPublish, module));
                 BuildableIvyModulePublishMetaData publishMetaData = componentMetaData.toPublishMetaData();
                 if (descriptor != null) {
-                    Artifact artifact = MDArtifact.newIvyArtifact(componentMetaData.getModuleDescriptor());
+                    Artifact artifact = MDArtifact.newIvyArtifact(publishMetaData.getModuleDescriptor());
                     publishMetaData.addArtifact(artifact, descriptor);
                 }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
index d870833..8b14482 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
@@ -17,9 +17,12 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.internal.component.external.model.IvyModuleArtifactPublishMetaData;
 
 import java.io.File;
+import java.util.Collection;
 
 public interface IvyModuleDescriptorWriter {
     public void write(ModuleDescriptor md, File output);
+    public void write(ModuleDescriptor md, Collection<IvyModuleArtifactPublishMetaData> artifacts, File output);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
index 178b5e7..0b582e7 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
@@ -15,8 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+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;
@@ -24,9 +23,12 @@ 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.internal.component.model.IvyArtifactName;
 import org.gradle.util.GUtil;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 
 import static java.util.Collections.emptyMap;
 
@@ -82,9 +84,25 @@ public class IvyUtil {
         return new ArtifactId(createModuleId(org, module), name, type, ext);
     }
 
-    public static DefaultModuleDescriptor createModuleDescriptor(DependencyDescriptor dependencyDescriptor) {
-        DefaultModuleDescriptor moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(dependencyDescriptor.getDependencyRevisionId(), dependencyDescriptor.getAllDependencyArtifacts());
-        moduleDescriptor.setStatus("integration");
+    public static DefaultModuleDescriptor createModuleDescriptor(ModuleComponentIdentifier componentIdentifier, Set<IvyArtifactName> componentArtifacts) {
+        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(componentIdentifier);
+
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(moduleRevisionId, "integration", null, true);
+        moduleDescriptor.addConfiguration(new Configuration(ModuleDescriptor.DEFAULT_CONFIGURATION));
+        moduleDescriptor.setLastModified(System.currentTimeMillis());
+
+        for (IvyArtifactName artifactName : componentArtifacts) {
+            addArtifact(moduleDescriptor, artifactName.getName(), artifactName.getType(), artifactName.getExtension(), artifactName.getAttributes());
+        }
+
+        if (componentArtifacts.isEmpty()) {
+            addArtifact(moduleDescriptor, componentIdentifier.getModule(), "jar", "jar", Collections.<String, String>emptyMap());
+        }
+
         return moduleDescriptor;
     }
+
+    private static void addArtifact(DefaultModuleDescriptor moduleDescriptor, String name, String type, String extension, Map<String, String> extraAttributes) {
+        moduleDescriptor.addArtifact(ModuleDescriptor.DEFAULT_CONFIGURATION, new MDArtifact(moduleDescriptor, name, type, extension, null, extraAttributes));
+    }
 }
\ No newline at end of file
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
index c018779..0f4c312 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
@@ -20,9 +20,12 @@ 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.util.extendable.ExtendableItem;
+import org.gradle.api.Transformer;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.xml.SimpleXmlWriter;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.component.external.model.IvyModuleArtifactPublishMetaData;
+import org.gradle.internal.xml.SimpleXmlWriter;
+import org.gradle.util.CollectionUtils;
 
 import java.io.*;
 import java.lang.reflect.Field;
@@ -42,30 +45,29 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
         dependencyConfigField.setAccessible(true);
     }
 
-    private void writeTo(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
-        writer.startElement("ivy-module");
-        writer.attribute("version", "2.0");
-
-        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);
+    @Override
+    public void write(ModuleDescriptor md, File output) {
+        doWrite(md, CollectionUtils.toList(md.getAllArtifacts()), output);
+    }
 
-        writer.endElement();
+    @Override
+    public void write(ModuleDescriptor md, Collection<IvyModuleArtifactPublishMetaData> artifacts, File output) {
+        List<Artifact> ivyArtifacts = CollectionUtils.collect(artifacts, new Transformer<Artifact, IvyModuleArtifactPublishMetaData>() {
+            @Override
+            public Artifact transform(IvyModuleArtifactPublishMetaData ivyModuleArtifactPublishMetaData) {
+                return ivyModuleArtifactPublishMetaData.toIvyArtifact();
+            }
+        });
+        doWrite(md, ivyArtifacts, output);
     }
 
-    public void write(ModuleDescriptor md, File output) {
+    private void doWrite(ModuleDescriptor md, Collection<Artifact> artifacts, File output) {
         try {
             output.getParentFile().mkdirs();
             OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
             try {
                 SimpleXmlWriter xmlWriter = new SimpleXmlWriter(outputStream, "  ");
-                writeTo(md, xmlWriter);
+                writeTo(md, artifacts, xmlWriter);
                 xmlWriter.flush();
             } finally {
                 outputStream.close();
@@ -75,6 +77,23 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
         }
     }
 
+    private void writeTo(ModuleDescriptor md, Collection<Artifact> artifacts, SimpleXmlWriter writer) throws IOException {
+        writer.startElement("ivy-module");
+        writer.attribute("version", "2.0");
+
+        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(artifacts, writer);
+        printDependencies(md, writer);
+
+        writer.endElement();
+    }
+
     private void printDependencies(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
         DependencyDescriptor[] dds = md.getDependencies();
         if (dds.length > 0) {
@@ -263,11 +282,9 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
         }
     }
 
-    private static void printPublications(ModuleDescriptor md, SimpleXmlWriter writer) throws IOException {
+    private static void printPublications(Collection<Artifact> artifacts, SimpleXmlWriter writer) throws IOException {
         writer.startElement("publications");
-        Artifact[] artifacts = md.getAllArtifacts();
-        for (int i = 0; i < artifacts.length; i++) {
-            Artifact artifact = artifacts[i];
+        for (Artifact artifact : artifacts) {
             writer.startElement("artifact");
             writer.attribute("name", artifact.getName());
             writer.attribute("type", artifact.getType());
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java
index 54324cb..00645af 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/LocalComponentFactory.java
@@ -15,12 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.artifacts.ModuleInternal;
-import org.gradle.internal.component.local.model.MutableLocalComponentMetaData;
-
-import java.util.Set;
+import org.gradle.internal.component.local.model.LocalComponentMetaData;
 
 public interface LocalComponentFactory {
-    MutableLocalComponentMetaData convert(Set<? extends Configuration> configurations, ModuleInternal module);
+    boolean canConvert(Object source);
+
+    LocalComponentMetaData convert(Object source);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
index 5c55884..fa4edc0 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
@@ -17,11 +17,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 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.GlobalDependencyResolutionRules;
-import org.gradle.api.internal.artifacts.ResolverResults;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.*;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.specs.Spec;
 import org.gradle.util.CollectionUtils;
@@ -38,12 +34,21 @@ public class SelfResolvingDependencyResolver implements ArtifactDependencyResolv
         this.resolver = resolver;
     }
 
-    public void resolve(ConfigurationInternal configuration,
+    public void resolve(ResolveContext resolveContext,
                         List<? extends ResolutionAwareRepository> repositories,
                         GlobalDependencyResolutionRules metadataHandler,
                         ResolverResults results) throws ResolveException {
-        resolver.resolve(configuration, repositories, metadataHandler, results);
+        resolver.resolve(resolveContext, repositories, metadataHandler, results);
+    }
+
+    public void resolveArtifacts(ResolveContext contextInternal,
+                                 List<? extends ResolutionAwareRepository> repositories,
+                                 GlobalDependencyResolutionRules metadataHandler,
+                                 ResolverResults results) throws ResolveException {
+        resolver.resolveArtifacts(contextInternal, repositories, metadataHandler, results);
+
         ResolvedConfiguration resolvedConfiguration = results.getResolvedConfiguration();
+        Configuration configuration = (Configuration) contextInternal;
         Set<Dependency> dependencies = configuration.getAllDependencies();
         CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
         SelfResolvingFilesProvider provider = new SelfResolvingFilesProvider(resolveContext, dependencies);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
index cd3a427..643757f 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
@@ -18,12 +18,9 @@ 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.GlobalDependencyResolutionRules;
-import org.gradle.api.internal.artifacts.ResolverResults;
+import org.gradle.api.internal.artifacts.*;
 import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.DefaultResolvedProjectConfigurationResultBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResults;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DefaultResolutionResultBuilder;
@@ -44,18 +41,28 @@ public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements Artif
         this.componentIdentifierFactory = componentIdentifierFactory;
     }
 
-    public void resolve(ConfigurationInternal configuration,
+    public void resolve(ResolveContext resolveContext,
                         List<? extends ResolutionAwareRepository> repositories,
                         GlobalDependencyResolutionRules metadataHandler,
                         ResolverResults results) throws ResolveException {
-        if (configuration.getAllDependencies().isEmpty()) {
-            ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(configuration.getModule());
-            ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(configuration.getModule());
+        if (resolveContext instanceof Configuration && resolveContext.getAllDependencies().isEmpty()) {
+            ModuleInternal module = ((DependencyMetaDataProvider) resolveContext).getModule();
+            ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(module);
+            ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(module);
             ResolutionResult emptyResult = new DefaultResolutionResultBuilder().start(id, componentIdentifier).complete();
-            ResolvedProjectConfigurationResults emptyProjectResult = new DefaultResolvedProjectConfigurationResultBuilder().complete();
-            results.resolved(new EmptyResolvedConfiguration(), emptyResult, emptyProjectResult);
+            ResolvedProjectConfigurationResults emptyProjectResult = new DefaultResolvedProjectConfigurationResultBuilder(false).complete();
+            results.resolved(emptyResult, emptyProjectResult);
         } else {
-            dependencyResolver.resolve(configuration, repositories, metadataHandler, results);
+            dependencyResolver.resolve(resolveContext, repositories, metadataHandler, results);
+        }
+    }
+
+    @Override
+    public void resolveArtifacts(ResolveContext resolveContext, List<? extends ResolutionAwareRepository> repositories, GlobalDependencyResolutionRules metadataHandler, ResolverResults results) throws ResolveException {
+        if (resolveContext.getAllDependencies().isEmpty()) {
+            results.withResolvedConfiguration(new EmptyResolvedConfiguration());
+        } else {
+            dependencyResolver.resolveArtifacts(resolveContext, repositories, metadataHandler, results);
         }
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
index 663c864..1ed993d 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
@@ -24,7 +24,7 @@ import org.gradle.api.artifacts.component.ComponentIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
 import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData;
 import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData;
-import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
@@ -40,31 +40,27 @@ public class ClientModuleResolver implements ComponentMetaDataResolver {
         this.dependencyDescriptorFactory = dependencyDescriptorFactory;
     }
 
-    public void resolve(DependencyMetaData dependency, ComponentIdentifier identifier, BuildableComponentResolveResult result) {
-        resolver.resolve(dependency, identifier, result);
+    public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
+        resolver.resolve(identifier, componentOverrideMetadata, result);
 
         if (result.getFailure() != null) {
             return;
         }
-        if (dependency instanceof DslOriginDependencyMetaData) {
-            ModuleDependency maybeClientModule = ((DslOriginDependencyMetaData) dependency).getSource();
-            if (maybeClientModule instanceof ClientModule) {
-                ClientModule clientModule = (ClientModule) maybeClientModule;
+        ClientModule clientModule = componentOverrideMetadata.getClientModule();
+        if (clientModule != null) {
+            MutableModuleComponentResolveMetaData clientModuleMetaData = ((MutableModuleComponentResolveMetaData)result.getMetaData()).copy();
+            addClientModuleDependencies(clientModule, clientModuleMetaData);
 
-                MutableModuleComponentResolveMetaData clientModuleMetaData = ((MutableModuleComponentResolveMetaData)result.getMetaData()).copy();
-                addClientModuleDependencies(clientModule, clientModuleMetaData);
+            setClientModuleArtifact(clientModuleMetaData);
 
-                setClientModuleArtifact(clientModuleMetaData);
-
-                result.setMetaData(clientModuleMetaData);
-            }
+            result.setMetaData(clientModuleMetaData);
         }
     }
 
     private void addClientModuleDependencies(ClientModule clientModule, MutableModuleComponentResolveMetaData clientModuleMetaData) {
         List<DependencyMetaData> dependencies = Lists.newArrayList();
         for (ModuleDependency moduleDependency : clientModule.getDependencies()) {
-            DependencyMetaData dependencyMetaData = dependencyDescriptorFactory.createDependencyDescriptor(moduleDependency.getConfiguration(), clientModuleMetaData.getDescriptor(), moduleDependency);
+            DependencyMetaData dependencyMetaData = dependencyDescriptorFactory.createDependencyDescriptor(moduleDependency.getConfiguration(), moduleDependency);
             dependencies.add(dependencyMetaData);
         }
         clientModuleMetaData.setDependencies(dependencies);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetails.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetails.java
new file mode 100644
index 0000000..d1358df
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetails.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.dependencysubstitution;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
+import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
+
+public class DefaultDependencyResolveDetails implements DependencyResolveDetailsInternal {
+
+    private final DependencySubstitutionInternal delegate;
+    private ModuleVersionSelector target;
+
+    public DefaultDependencyResolveDetails(DependencySubstitutionInternal delegate) {
+        this.delegate = delegate;
+        target = determineTarget(delegate);
+    }
+
+    private static ModuleVersionSelector determineTarget(DependencySubstitutionInternal delegate) {
+        // Temporary logic until we add DependencySubstitution back in
+        if (delegate.getTarget() instanceof ModuleComponentSelector) {
+            ModuleComponentSelector moduleComponentSelector = (ModuleComponentSelector) delegate.getTarget();
+            return DefaultModuleVersionSelector.newSelector(moduleComponentSelector.getGroup(), moduleComponentSelector.getModule(), moduleComponentSelector.getVersion());
+        }
+        // If the target is a project component, it must be unmodified from the requested
+        return delegate.getOldRequested();
+    }
+
+    @Override
+    public ModuleVersionSelector getRequested() {
+        return delegate.getOldRequested();
+    }
+
+    @Override
+    public void useVersion(String version) {
+        useVersion(version, VersionSelectionReasons.SELECTED_BY_RULE);
+    }
+
+    @Override
+    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.");
+        }
+
+        if (!version.equals(target.getVersion())) {
+            target = DefaultModuleVersionSelector.newSelector(target.getGroup(), target.getName(), version);
+            delegate.useTarget(DefaultModuleComponentSelector.newSelector(target), selectionReason);
+        } else {
+            // Still 'updated' with reason when version remains the same.
+            delegate.useTarget(delegate.getTarget(), selectionReason);
+        }
+    }
+
+    @Override
+    public void useTarget(Object notation) {
+        target = ModuleVersionSelectorParsers.parser().parseNotation(notation);
+        delegate.useTarget(DefaultModuleComponentSelector.newSelector(target), VersionSelectionReasons.SELECTED_BY_RULE);
+    }
+
+    @Override
+    public ComponentSelectionReason getSelectionReason() {
+        return delegate.getSelectionReason();
+    }
+
+    @Override
+    public ModuleVersionSelector getTarget() {
+        return target;
+    }
+
+    @Override
+    public boolean isUpdated() {
+        return delegate.isUpdated();
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitution.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitution.java
new file mode 100644
index 0000000..861d477
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitution.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.result.ComponentSelectionReason;
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
+import org.gradle.api.internal.artifacts.dsl.ComponentSelectorParsers;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+
+public class DefaultDependencySubstitution implements DependencySubstitutionInternal {
+    private final ComponentSelector requested;
+    private final ModuleVersionSelector oldRequested;
+    private ComponentSelectionReason selectionReason;
+    private ComponentSelector target;
+
+    public DefaultDependencySubstitution(ComponentSelector requested, ModuleVersionSelector oldRequested) {
+        this.requested = requested;
+        this.target = requested;
+        this.oldRequested = oldRequested;
+    }
+
+    @Override
+    public ComponentSelector getRequested() {
+        return requested;
+    }
+
+    @Override
+    public ModuleVersionSelector getOldRequested() {
+        return oldRequested;
+    }
+
+    @Override
+    public void useTarget(Object notation) {
+        useTarget(notation, VersionSelectionReasons.SELECTED_BY_RULE);
+    }
+
+    @Override
+    public void useTarget(Object notation, ComponentSelectionReason selectionReason) {
+        this.target = ComponentSelectorParsers.parser().parseNotation(notation);
+        this.selectionReason = selectionReason;
+        validateTarget(target);
+    }
+
+    @Override
+    public ComponentSelectionReason getSelectionReason() {
+        return selectionReason;
+    }
+
+    @Override
+    public ComponentSelector getTarget() {
+        return target;
+    }
+
+    @Override
+    public boolean isUpdated() {
+        return selectionReason != null;
+    }
+
+    public static void validateTarget(ComponentSelector componentSelector) {
+        if (componentSelector instanceof UnversionedModuleComponentSelector) {
+            throw new InvalidUserDataException("Must specify version for target of dependency substitution");
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java
new file mode 100644
index 0000000..ef682d5
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.DependencyResolveDetails;
+import org.gradle.api.artifacts.DependencySubstitution;
+import org.gradle.api.artifacts.DependencySubstitutions;
+import org.gradle.api.artifacts.ModuleIdentifier;
+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.DependencySubstitutionInternal;
+import org.gradle.api.internal.artifacts.configurations.MutationValidator;
+import org.gradle.internal.Actions;
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector;
+import org.gradle.internal.exceptions.DiagnosticsVisitor;
+import org.gradle.internal.typeconversion.*;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultDependencySubstitutions implements DependencySubstitutionsInternal {
+    private final Set<Action<? super DependencySubstitution>> substitutionRules;
+    private final NotationParser<Object, ComponentSelector> moduleSelectorNotationParser;
+    private final NotationParser<Object, ComponentSelector> projectSelectorNotationParser;
+
+    private MutationValidator mutationValidator = MutationValidator.IGNORE;
+    private boolean hasDependencySubstitutionRule;
+
+    public DefaultDependencySubstitutions() {
+        this(new LinkedHashSet<Action<? super DependencySubstitution>>());
+    }
+
+    DefaultDependencySubstitutions(Set<Action<? super DependencySubstitution>> substitutionRules) {
+        this.substitutionRules = substitutionRules;
+        this.moduleSelectorNotationParser = createModuleSelectorNotationParser();
+        this.projectSelectorNotationParser = createProjectSelectorNotationParser();
+    }
+
+    @Override
+    public boolean hasDependencySubstitutionRules() {
+        return hasDependencySubstitutionRule;
+    }
+
+    @Override
+    public Action<DependencySubstitution> getDependencySubstitutionRule() {
+        return Actions.composite(substitutionRules);
+    }
+
+    private void addRule(Action<? super DependencySubstitution> rule) {
+        mutationValidator.validateMutation(MutationValidator.MutationType.STRATEGY);
+        substitutionRules.add(rule);
+    }
+
+    @Override
+    public DependencySubstitutions all(Action<? super DependencySubstitution> rule) {
+        addRule(rule);
+        hasDependencySubstitutionRule = true;
+        return this;
+    }
+
+    @Override
+    public DependencySubstitutions allWithDependencyResolveDetails(Action<? super DependencyResolveDetails> rule) {
+        addRule(new DependencyResolveDetailsWrapperAction(rule));
+        return this;
+    }
+
+    @Override
+    public ComponentSelector module(String notation) {
+        return moduleSelectorNotationParser.parseNotation(notation);
+    }
+
+    @Override
+    public ComponentSelector project(final String path) {
+        return projectSelectorNotationParser.parseNotation(path);
+    }
+
+    @Override
+    public Substitution substitute(final ComponentSelector substituted) {
+        return new Substitution() {
+            @Override
+            public void with(ComponentSelector substitute) {
+                DefaultDependencySubstitution.validateTarget(substitute);
+
+                if (substituted instanceof UnversionedModuleComponentSelector) {
+                    final ModuleIdentifier moduleId = ((UnversionedModuleComponentSelector) substituted).getModuleIdentifier();
+                    all(new ModuleMatchDependencySubstitutionAction(moduleId, substitute));
+                } else {
+                    all(new ExactMatchDependencySubstitutionAction(substituted, substitute));
+                }
+            }
+        };
+    }
+
+    @Override
+    public void setMutationValidator(MutationValidator validator) {
+        mutationValidator = validator;
+    }
+
+    @Override
+    public DependencySubstitutionsInternal copy() {
+        return new DefaultDependencySubstitutions(new LinkedHashSet<Action<? super DependencySubstitution>>(substitutionRules));
+    }
+
+    private static NotationParser<Object, ComponentSelector> createModuleSelectorNotationParser() {
+        return NotationParserBuilder
+                .toType(ComponentSelector.class)
+                .converter(new ModuleSelectorStringNotationConverter())
+                .toComposite();
+    }
+
+    private static NotationParser<Object, ComponentSelector> createProjectSelectorNotationParser() {
+        return NotationParserBuilder
+                .toType(ComponentSelector.class)
+                .fromCharSequence(new ProjectPathConverter())
+                .toComposite();
+    }
+
+    private static class ProjectPathConverter implements NotationConverter<String, ProjectComponentSelector> {
+        @Override
+        public void describe(DiagnosticsVisitor visitor) {
+            visitor.example("Project paths, e.g. ':api'.");
+        }
+
+        @Override
+        public void convert(String notation, NotationConvertResult<? super ProjectComponentSelector> result) throws TypeConversionException {
+            result.converted(DefaultProjectComponentSelector.newSelector(notation));
+        }
+    }
+
+    private class ExactMatchDependencySubstitutionAction implements Action<DependencySubstitution> {
+        private final ComponentSelector substituted;
+        private final ComponentSelector substitute;
+
+        public ExactMatchDependencySubstitutionAction(ComponentSelector substituted, ComponentSelector substitute) {
+            this.substituted = substituted;
+            this.substitute = substitute;
+        }
+
+        @Override
+        public void execute(DependencySubstitution dependencySubstitution) {
+            if (substituted.equals(dependencySubstitution.getRequested())) {
+                dependencySubstitution.useTarget(substitute);
+            }
+        }
+    }
+
+    private static class ModuleMatchDependencySubstitutionAction implements Action<DependencySubstitution> {
+        private final ModuleIdentifier moduleId;
+        private final ComponentSelector substitute;
+
+        public ModuleMatchDependencySubstitutionAction(ModuleIdentifier moduleId, ComponentSelector substitute) {
+            this.moduleId = moduleId;
+            this.substitute = substitute;
+        }
+
+        @Override
+        public void execute(DependencySubstitution dependencySubstitution) {
+            if (dependencySubstitution.getRequested() instanceof ModuleComponentSelector) {
+                ModuleComponentSelector requested = (ModuleComponentSelector) dependencySubstitution.getRequested();
+                if (moduleId.getGroup().equals(requested.getGroup()) && moduleId.getName().equals(requested.getModule())) {
+                    dependencySubstitution.useTarget(substitute);
+                }
+            }
+        }
+    }
+
+    private static class DependencyResolveDetailsWrapperAction implements Action<DependencySubstitution> {
+        private final Action<? super DependencyResolveDetails> delegate;
+
+        public DependencyResolveDetailsWrapperAction(Action<? super DependencyResolveDetails> delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void execute(DependencySubstitution substitution) {
+            DefaultDependencyResolveDetails details = new DefaultDependencyResolveDetails((DependencySubstitutionInternal) substitution);
+            delegate.execute(details);
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolver.java
new file mode 100644
index 0000000..4a34963
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.DependencySubstitution;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
+import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
+import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
+
+public class DependencySubstitutionResolver implements DependencyToComponentIdResolver {
+    private final DependencyToComponentIdResolver resolver;
+    private final Action<DependencySubstitution> rule;
+
+    public DependencySubstitutionResolver(DependencyToComponentIdResolver resolver, Action<DependencySubstitution> rule) {
+        this.resolver = resolver;
+        this.rule = rule;
+    }
+
+    public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
+        ComponentSelector selector = dependency.getSelector();
+        DependencySubstitutionInternal details = new DefaultDependencySubstitution(selector, dependency.getRequested());
+        try {
+            rule.execute(details);
+        } catch (Throwable e) {
+            result.failed(new ModuleVersionResolveException(selector, e));
+            return;
+        }
+        if (details.isUpdated()) {
+            resolver.resolve(dependency.withTarget(details.getTarget()), result);
+            result.setSelectionReason(details.getSelectionReason());
+            return;
+        }
+        resolver.resolve(dependency, result);
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionsInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionsInternal.java
new file mode 100644
index 0000000..892738e
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.DependencyResolveDetails;
+import org.gradle.api.artifacts.DependencySubstitution;
+import org.gradle.api.artifacts.DependencySubstitutions;
+import org.gradle.api.internal.artifacts.configurations.MutationValidator;
+
+public interface DependencySubstitutionsInternal extends DependencySubstitutions {
+    boolean hasDependencySubstitutionRules();
+
+    Action<DependencySubstitution> getDependencySubstitutionRule();
+
+    DependencySubstitutions allWithDependencyResolveDetails(Action<? super DependencyResolveDetails> rule);
+
+    void setMutationValidator(MutationValidator validator);
+
+    DependencySubstitutionsInternal copy();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverter.java
new file mode 100644
index 0000000..101d041
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
+import org.gradle.internal.exceptions.DiagnosticsVisitor;
+import org.gradle.internal.typeconversion.TypedNotationConverter;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.util.GUtil;
+
+import static org.gradle.api.internal.notations.ModuleIdentifierNotationConverter.validate;
+
+class ModuleSelectorStringNotationConverter extends TypedNotationConverter<String, ComponentSelector> {
+    public ModuleSelectorStringNotationConverter() {
+        super(String.class);
+    }
+
+    /**
+     * Empty String for either group or module name is not allowed.
+     */
+    protected ComponentSelector parseType(String notation) {
+        assert notation != null;
+        String[] split = notation.split(":");
+
+        if (split.length < 2 || split.length > 3) {
+            throw new UnsupportedNotationException(notation);
+        }
+        String group = validate(split[0].trim(), notation);
+        String name = validate(split[1].trim(), notation);
+
+        if (split.length == 2) {
+            return new UnversionedModuleComponentSelector(group, name);
+        }
+        String version = split[2].trim();
+        if (!GUtil.isTrue(version)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        return DefaultModuleComponentSelector.newSelector(group, name, version);
+    }
+
+    @Override
+    public void describe(DiagnosticsVisitor visitor) {
+        visitor.candidate("String describing the module in 'group:name' format").example("'org.gradle:gradle-core'.");
+        visitor.candidate("String describing the selector in 'group:name:version' format").example("'org.gradle:gradle-core:1.+'.");
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/UnversionedModuleComponentSelector.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/UnversionedModuleComponentSelector.java
new file mode 100644
index 0000000..e71917b
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/UnversionedModuleComponentSelector.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution;
+
+import com.google.common.base.Objects;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+
+class UnversionedModuleComponentSelector implements ComponentSelector {
+    private final ModuleIdentifier moduleIdentifier;
+
+    UnversionedModuleComponentSelector(String group, String name) {
+        this.moduleIdentifier = DefaultModuleIdentifier.newId(group, name);
+    }
+
+    public ModuleIdentifier getModuleIdentifier() {
+        return moduleIdentifier;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return moduleIdentifier.toString() + ":*";
+    }
+
+    @Override
+    public boolean matchesStrictly(ComponentIdentifier identifier) {
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        UnversionedModuleComponentSelector that = (UnversionedModuleComponentSelector) o;
+        return Objects.equal(moduleIdentifier, that.moduleIdentifier);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(moduleIdentifier);
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BaseModuleComponentRepositoryAccess.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BaseModuleComponentRepositoryAccess.java
index 0c7cdf3..ae3da6c 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BaseModuleComponentRepositoryAccess.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BaseModuleComponentRepositoryAccess.java
@@ -38,8 +38,8 @@ public class BaseModuleComponentRepositoryAccess implements ModuleComponentRepos
         delegate.listModuleVersions(dependency, result);
     }
 
-    public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-        delegate.resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+    public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+        delegate.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
     }
 
     public void resolveModuleArtifacts(ComponentResolveMetaData component, ArtifactType artifactType, BuildableArtifactSetResolveResult result) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockReleasingModuleComponentsRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockReleasingModuleComponentsRepository.java
index 88c7c35..a4fc830 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockReleasingModuleComponentsRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockReleasingModuleComponentsRepository.java
@@ -64,10 +64,11 @@ public class CacheLockReleasingModuleComponentsRepository extends BaseModuleComp
             });
         }
 
-        public void resolveComponentMetaData(final DependencyMetaData dependency, final ModuleComponentIdentifier moduleComponentIdentifier, final BuildableModuleComponentMetaDataResolveResult result) {
-            cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dependency, name), new Runnable() {
+        public void resolveComponentMetaData(final ModuleComponentIdentifier moduleComponentIdentifier,
+                                             final ComponentOverrideMetadata requestMetaData, final BuildableModuleComponentMetaDataResolveResult result) {
+            cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", moduleComponentIdentifier, name), new Runnable() {
                 public void run() {
-                    delegate.resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+                    delegate.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
                 }
             });
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
index c307ed1..d51ec41 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepository.java
@@ -144,17 +144,17 @@ public class CachingModuleComponentRepository implements ModuleComponentReposito
             }
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
             // First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
-            delegate.getLocalAccess().resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+            delegate.getLocalAccess().resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
             if (result.hasResult()) {
                 return;
             }
 
-            resolveComponentMetaDataFromCache(dependency, moduleComponentIdentifier, result);
+            resolveComponentMetaDataFromCache(moduleComponentIdentifier, requestMetaData, result);
         }
 
-        private void resolveComponentMetaDataFromCache(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        private void resolveComponentMetaDataFromCache(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
             ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.getCachedModuleDescriptor(delegate, moduleComponentIdentifier);
             if (cachedMetaData == null) {
                 return;
@@ -172,7 +172,7 @@ public class CachingModuleComponentRepository implements ModuleComponentReposito
             }
             MutableModuleComponentResolveMetaData metaData = cachedMetaData.getMetaData();
             metadataProcessor.processMetadata(metaData);
-            if (dependency.isChanging() || metaData.isChanging()) {
+            if (requestMetaData.isChanging() || metaData.isChanging()) {
                 if (cachePolicy.mustRefreshChangingModule(moduleComponentIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
                     LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
                     return;
@@ -291,9 +291,10 @@ public class CachingModuleComponentRepository implements ModuleComponentReposito
             }
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-            DependencyMetaData forced = dependency.withChanging();
-            delegate.getRemoteAccess().resolveComponentMetaData(forced, moduleComponentIdentifier, result);
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+            ComponentOverrideMetadata forced = requestMetaData.withChanging();
+
+            delegate.getRemoteAccess().resolveComponentMetaData(moduleComponentIdentifier, forced, result);
             switch (result.getState()) {
                 case Missing:
                     moduleMetaDataCache.cacheMissing(delegate, moduleComponentIdentifier);
@@ -303,7 +304,7 @@ public class CachingModuleComponentRepository implements ModuleComponentReposito
                     ModuleSource moduleSource = metaData.getSource();
                     ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.cacheMetaData(delegate, metaData);
                     metadataProcessor.processMetadata(metaData);
-                    moduleSource = new CachingModuleSource(cachedMetaData.getDescriptorHash(), dependency.isChanging() || metaData.isChanging(), moduleSource);
+                    moduleSource = new CachingModuleSource(cachedMetaData.getDescriptorHash(), requestMetaData.isChanging() || metaData.isChanging(), moduleSource);
                     metaData.setSource(moduleSource);
                     result.resolved(metaData);
                     break;
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ComponentMetaDataResolveState.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ComponentMetaDataResolveState.java
index 63c0e2a..85f8128 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ComponentMetaDataResolveState.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ComponentMetaDataResolveState.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
-import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
 import org.gradle.internal.resolve.result.DefaultBuildableModuleComponentMetaDataResolveResult;
 import org.gradle.internal.resolve.result.ResourceAwareResolveResult;
@@ -25,15 +25,15 @@ import org.gradle.internal.resolve.result.ResourceAwareResolveResult;
 class ComponentMetaDataResolveState {
     private final DefaultBuildableModuleComponentMetaDataResolveResult resolveResult = new DefaultBuildableModuleComponentMetaDataResolveResult();
     private final VersionedComponentChooser versionedComponentChooser;
-    private final DependencyMetaData dependency;
+    private final ComponentOverrideMetadata componentOverrideMetadata;
     private final ModuleComponentIdentifier componentIdentifier;
     final ModuleComponentRepository repository;
 
     private boolean searchedLocally;
     private boolean searchedRemotely;
 
-    public ComponentMetaDataResolveState(DependencyMetaData dependency, ModuleComponentIdentifier componentIdentifier, ModuleComponentRepository repository, VersionedComponentChooser versionedComponentChooser) {
-        this.dependency = dependency;
+    public ComponentMetaDataResolveState(ModuleComponentIdentifier componentIdentifier, ComponentOverrideMetadata componentOverrideMetadata, ModuleComponentRepository repository, VersionedComponentChooser versionedComponentChooser) {
+        this.componentOverrideMetadata = componentOverrideMetadata;
         this.componentIdentifier = componentIdentifier;
         this.repository = repository;
         this.versionedComponentChooser = versionedComponentChooser;
@@ -63,7 +63,7 @@ class ComponentMetaDataResolveState {
     }
 
     protected void process(ModuleComponentRepositoryAccess moduleAccess) {
-        moduleAccess.resolveComponentMetaData(dependency, componentIdentifier, resolveResult);
+        moduleAccess.resolveComponentMetaData(componentIdentifier, componentOverrideMetadata, resolveResult);
         if (resolveResult.getState() == BuildableModuleComponentMetaDataResolveResult.State.Resolved) {
             if (versionedComponentChooser.isRejectedComponent(componentIdentifier, new MetadataProvider(resolveResult))) {
                 resolveResult.missing();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooser.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooser.java
index 3c8077d..8a368de 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooser.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooser.java
@@ -25,7 +25,6 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionC
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelector;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
-import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.resolve.result.BuildableComponentSelectionResult;
 import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
 import org.gradle.internal.rules.SpecRuleAction;
@@ -68,9 +67,8 @@ class DefaultVersionedComponentChooser implements VersionedComponentChooser {
         return componentResolveMetaData.isGenerated();
     }
 
-    public void selectNewestMatchingComponent(Collection<? extends ModuleComponentResolveState> versions, DependencyMetaData dependency, BuildableComponentSelectionResult result) {
-        ModuleVersionSelector requestedModule = dependency.getRequested();
-        VersionSelector requestedVersion = versionSelectorScheme.parseSelector(requestedModule.getVersion());
+    public void selectNewestMatchingComponent(Collection<? extends ModuleComponentResolveState> versions, BuildableComponentSelectionResult result, ModuleVersionSelector requested) {
+        VersionSelector requestedVersion = versionSelectorScheme.parseSelector(requested.getVersion());
         Collection<SpecRuleAction<? super ComponentSelection>> rules = componentSelectionRules.getRules();
 
         for (ModuleComponentResolveState candidate : sortLatestFirst(versions)) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DynamicVersionResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DynamicVersionResolver.java
index c67bda1..32a4d27 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DynamicVersionResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DynamicVersionResolver.java
@@ -22,6 +22,7 @@ import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
 import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
+import org.gradle.internal.component.model.DefaultComponentOverrideMetadata;
 import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.resolve.ModuleVersionNotFoundException;
 import org.gradle.internal.resolve.ModuleVersionResolveException;
@@ -55,7 +56,7 @@ public class DynamicVersionResolver implements DependencyToComponentIdResolver {
 
     public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
         ModuleVersionSelector requested = dependency.getRequested();
-        LOGGER.debug("Attempting to resolve {} using repositories {}", requested, repositoryNames);
+        LOGGER.debug("Attempting to resolve version for {} using repositories {}", requested, repositoryNames);
         List<Throwable> errors = new ArrayList<Throwable>();
 
         List<RepositoryResolveState> resolveStates = new ArrayList<RepositoryResolveState>();
@@ -170,9 +171,11 @@ public class DynamicVersionResolver implements DependencyToComponentIdResolver {
         private final ModuleComponentRepository repository;
         private final AttemptCollector attemptCollector;
         private final DependencyMetaData dependency;
+        private final ModuleVersionSelector selector;
 
         public RepositoryResolveState(DependencyMetaData dependency, ModuleComponentRepository repository) {
             this.dependency = dependency;
+            this.selector = dependency.getRequested();
             this.repository = repository;
             this.attemptCollector = new AttemptCollector();
             versionListingResult = new VersionListResult(dependency, repository);
@@ -200,7 +203,7 @@ public class DynamicVersionResolver implements DependencyToComponentIdResolver {
 
         private void selectMatchingVersionAndResolve() {
             // TODO - reuse metaData if it was already fetched to select the component from the version list
-            versionedComponentChooser.selectNewestMatchingComponent(candidates(), dependency, componentSelectionResult);
+            versionedComponentChooser.selectNewestMatchingComponent(candidates(), componentSelectionResult, selector);
             switch (componentSelectionResult.getState()) {
                 // No version matching list: component is missing
                 case NoMatch:
@@ -291,7 +294,8 @@ public class DynamicVersionResolver implements DependencyToComponentIdResolver {
         }
 
         private void process(ModuleComponentRepositoryAccess access) {
-            access.resolveComponentMetaData(dependencyMetaData.withRequestedVersion(version), identifier, result);
+            DependencyMetaData dependency = dependencyMetaData.withRequestedVersion(version);
+            access.resolveComponentMetaData(identifier, DefaultComponentOverrideMetadata.forDependency(dependency), result);
             attemptCollector.execute(result);
         }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java
index e2c2804..86195c5 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java
@@ -84,9 +84,9 @@ public class ErrorHandlingModuleComponentRepository implements ModuleComponentRe
         }
 
         @Override
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
             try {
-                delegate.resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+                delegate.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
             } catch (Throwable throwable) {
                 result.failed(new ModuleVersionResolveException(moduleComponentIdentifier, throwable));
             }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccess.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccess.java
index 71dd980..1385415 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccess.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccess.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
 
@@ -50,8 +51,8 @@ class IvyDynamicResolveModuleComponentRepositoryAccess extends BaseModuleCompone
         return "Ivy dynamic resolve > " + getDelegate().toString();
     }
 
-    public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-        super.resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+    public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+        super.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
         if (result.getState() == BuildableModuleComponentMetaDataResolveResult.State.Resolved) {
             transformDependencies(result);
         }
@@ -61,7 +62,7 @@ class IvyDynamicResolveModuleComponentRepositoryAccess extends BaseModuleCompone
         MutableModuleComponentResolveMetaData metaData = result.getMetaData();
         List<DependencyMetaData> transformed = new ArrayList<DependencyMetaData>();
         for (DependencyMetaData dependency : metaData.getDependencies()) {
-            transformed.add(dependency.withRequestedVersion(dependency.getDescriptor().getDynamicConstraintDependencyRevisionId().getRevision()));
+            transformed.add(dependency.withRequestedVersion(dependency.getDynamicConstraintVersion()));
         }
         metaData.setDependencies(transformed);
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleComponentRepository.java
index 1e5c19d..36b2061 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleComponentRepository.java
@@ -57,10 +57,10 @@ public class LocalModuleComponentRepository extends BaseModuleComponentRepositor
             }
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-            delegate.getLocalAccess().resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+            delegate.getLocalAccess().resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
             if (!result.hasResult()) {
-                delegate.getRemoteAccess().resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+                delegate.getRemoteAccess().resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
             }
 
             if (result.getState() == BuildableModuleComponentMetaDataResolveResult.State.Resolved) {
@@ -99,7 +99,7 @@ public class LocalModuleComponentRepository extends BaseModuleComponentRepositor
         public void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionListingResolveResult result) {
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
         }
 
         public void resolveModuleArtifacts(ComponentResolveMetaData component, ArtifactType artifactType, BuildableArtifactSetResolveResult result) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepository.java
index 35a4197..7b7278f 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepository.java
@@ -18,9 +18,6 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 /**
  * A repository of module components.
- *
- * The plan is to sync this with {@link org.gradle.internal.resolve.resolver.DependencyToComponentResolver} and rename it
- * to have 'resolver' instead of 'repository' in its name.
  */
 public interface ModuleComponentRepository {
     String getId();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepositoryAccess.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepositoryAccess.java
index 073b99e..64fbb00 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepositoryAccess.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleComponentRepositoryAccess.java
@@ -26,6 +26,11 @@ import org.gradle.internal.resolve.result.BuildableModuleVersionListingResolveRe
 
 /**
  * Provides access to a repository of components that are identified by a ModuleComponentIdentifier.
+ *
+ * The plan is to eventually sync this with
+ * {@link org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver},
+ * {@link org.gradle.internal.resolve.resolver.ComponentMetaDataResolver} and
+ * {@link org.gradle.internal.resolve.resolver.ArtifactResolver}.
  */
 public interface ModuleComponentRepositoryAccess {
     /**
@@ -36,7 +41,7 @@ public interface ModuleComponentRepositoryAccess {
     /**
      * Resolves the metadata for a module component.
      */
-    void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result);
+    void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result);
 
     /**
      * Resolves a set of artifacts belonging to the given component, based on the supplied usage. Any failures are packaged up in the result.
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java
index ec2f55a..aa05ce8 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java
@@ -23,7 +23,6 @@ import org.gradle.internal.resolve.ModuleVersionNotFoundException;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
 import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
 import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
 import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
@@ -37,14 +36,10 @@ public class NoRepositoriesResolver implements RepositoryChain, DependencyToComp
         return this;
     }
 
-    public ComponentMetaDataResolver getComponentMetaDataResolver() {
+    public ComponentMetaDataResolver getComponentResolver() {
         return this;
     }
 
-    public DependencyToComponentResolver getDependencyResolver() {
-        throw new UnsupportedOperationException();
-    }
-
     public ArtifactResolver getArtifactResolver() {
         return this;
     }
@@ -53,7 +48,7 @@ public class NoRepositoriesResolver implements RepositoryChain, DependencyToComp
         result.failed(new ModuleVersionNotFoundException(dependency.getRequested(), String.format("Cannot resolve external dependency %s because no repositories are defined.", dependency.getRequested())));
     }
 
-    public void resolve(DependencyMetaData dependency, ComponentIdentifier identifier, BuildableComponentResolveResult result) {
+    public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java
index f71916e..24c367f 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChain.java
@@ -18,14 +18,11 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
 
 public interface RepositoryChain {
     public DependencyToComponentIdResolver getComponentIdResolver();
 
-    public ComponentMetaDataResolver getComponentMetaDataResolver();
-
-    public DependencyToComponentResolver getDependencyResolver();
+    public ComponentMetaDataResolver getComponentResolver();
 
     public ArtifactResolver getArtifactResolver();
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapter.java
deleted file mode 100644
index ec4d5d3..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapter.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.component.ComponentIdentifier;
-import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme;
-import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
-import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
-import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
-import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
-
-/**
- * Takes a dependency->meta-data resolver and presents it as separate dependency->id and id->meta-data resolvers.
- *
- * Short-circuits the dependency->id resolution for static versions.
- */
-public class RepositoryChainAdapter implements DependencyToComponentIdResolver, ComponentMetaDataResolver {
-    private final DependencyToComponentIdResolver dynamicRevisionResolver;
-    private final DependencyToComponentResolver metaDataResolver;
-    private final VersionSelectorScheme versionSelectorScheme;
-
-    public RepositoryChainAdapter(DependencyToComponentIdResolver dynamicRevisionResolver, DependencyToComponentResolver metaDataResolver, VersionSelectorScheme versionSelectorScheme) {
-        this.dynamicRevisionResolver = dynamicRevisionResolver;
-        this.metaDataResolver = metaDataResolver;
-        this.versionSelectorScheme = versionSelectorScheme;
-    }
-
-    public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
-        ModuleVersionSelector requested = dependency.getRequested();
-        if (versionSelectorScheme.parseSelector(requested.getVersion()).isDynamic()) {
-            dynamicRevisionResolver.resolve(dependency, result);
-        } else {
-            DefaultModuleComponentIdentifier id = new DefaultModuleComponentIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
-            DefaultModuleVersionIdentifier mvId = new DefaultModuleVersionIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
-            result.resolved(id, mvId);
-        }
-    }
-
-    public void resolve(DependencyMetaData dependency, ComponentIdentifier identifier, BuildableComponentResolveResult result) {
-        if (!(identifier instanceof ModuleComponentIdentifier)) {
-            throw new UnsupportedOperationException("Can resolve meta-data for module components only.");
-        }
-
-        // Force the requested version
-        ModuleComponentIdentifier moduleId = (ModuleComponentIdentifier) identifier;
-        dependency = dependency.withTarget(new DefaultModuleComponentSelector(moduleId.getGroup(), moduleId.getModule(), moduleId.getVersion()));
-
-        metaDataResolver.resolve(dependency, result);
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolver.java
new file mode 100644
index 0000000..9eec7f2
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolver.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
+import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
+import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
+import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RepositoryChainComponentMetaDataResolver implements ComponentMetaDataResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryChainComponentMetaDataResolver.class);
+
+    private final List<ModuleComponentRepository> repositories = new ArrayList<ModuleComponentRepository>();
+    private final List<String> repositoryNames = new ArrayList<String>();
+    private final VersionedComponentChooser versionedComponentChooser;
+    private final Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> metaDataFactory;
+
+    public RepositoryChainComponentMetaDataResolver(VersionedComponentChooser componentChooser, Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> metaDataFactory) {
+        this.versionedComponentChooser = componentChooser;
+        this.metaDataFactory = metaDataFactory;
+    }
+
+    public void add(ModuleComponentRepository repository) {
+        repositories.add(repository);
+        repositoryNames.add(repository.getName());
+    }
+
+    public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
+        if (!(identifier instanceof ModuleComponentIdentifier)) {
+            throw new UnsupportedOperationException("Can resolve meta-data for module components only.");
+        }
+
+        resolveModule((ModuleComponentIdentifier) identifier, componentOverrideMetadata, result);
+    }
+
+    private void resolveModule(ModuleComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
+        LOGGER.debug("Attempting to resolve component for {} using repositories {}", identifier, repositoryNames);
+
+        List<Throwable> errors = new ArrayList<Throwable>();
+
+        List<ComponentMetaDataResolveState> resolveStates = new ArrayList<ComponentMetaDataResolveState>();
+        for (ModuleComponentRepository repository : repositories) {
+            resolveStates.add(new ComponentMetaDataResolveState(identifier, componentOverrideMetadata, repository, versionedComponentChooser));
+        }
+
+        final RepositoryChainModuleResolution latestResolved = findBestMatch(resolveStates, errors);
+        if (latestResolved != null) {
+            LOGGER.debug("Using {} from {}", latestResolved.module.getId(), latestResolved.repository);
+            for (Throwable error : errors) {
+                LOGGER.debug("Discarding resolve failure.", error);
+            }
+
+            result.resolved(metaDataFactory.transform(latestResolved));
+            return;
+        }
+        if (!errors.isEmpty()) {
+            result.failed(new ModuleVersionResolveException(identifier, errors));
+        } else {
+            for (ComponentMetaDataResolveState resolveState : resolveStates) {
+                resolveState.applyTo(result);
+            }
+            result.notFound(identifier);
+        }
+    }
+
+    private RepositoryChainModuleResolution findBestMatch(List<ComponentMetaDataResolveState> resolveStates, Collection<Throwable> failures) {
+        LinkedList<ComponentMetaDataResolveState> queue = new LinkedList<ComponentMetaDataResolveState>();
+        queue.addAll(resolveStates);
+
+        LinkedList<ComponentMetaDataResolveState> missing = new LinkedList<ComponentMetaDataResolveState>();
+
+        // A first pass to do local resolves only
+        RepositoryChainModuleResolution best = findBestMatch(queue, failures, missing);
+        if (best != null) {
+            return best;
+        }
+
+        // Nothing found - do a second pass
+        queue.addAll(missing);
+        missing.clear();
+        return findBestMatch(queue, failures, missing);
+    }
+
+    private RepositoryChainModuleResolution findBestMatch(LinkedList<ComponentMetaDataResolveState> queue, Collection<Throwable> failures, Collection<ComponentMetaDataResolveState> missing) {
+        RepositoryChainModuleResolution best = null;
+        while (!queue.isEmpty()) {
+            ComponentMetaDataResolveState request = queue.removeFirst();
+            BuildableModuleComponentMetaDataResolveResult metaDataResolveResult;
+            try {
+                metaDataResolveResult = request.resolve();
+            } catch (Throwable t) {
+                failures.add(t);
+                continue;
+            }
+            switch (metaDataResolveResult.getState()) {
+                case Failed:
+                    failures.add(metaDataResolveResult.getFailure());
+                    break;
+                case Missing:
+                    // Queue this up for checking again later
+                    if (request.canMakeFurtherAttempts()) {
+                        missing.add(request);
+                    }
+                    break;
+                case Resolved:
+                    RepositoryChainModuleResolution moduleResolution = new RepositoryChainModuleResolution(request.repository, metaDataResolveResult.getMetaData());
+                    if (!metaDataResolveResult.getMetaData().isGenerated()) {
+                        return moduleResolution;
+                    }
+                    best = best != null ? best : moduleResolution;
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected state for resolution: " + metaDataResolveResult.getState());
+            }
+        }
+
+        return best;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.java
deleted file mode 100644
index 80c1c7a..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolver.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.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.gradle.api.Transformer;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
-import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
-import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.resolve.ModuleVersionResolveException;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
-import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
-import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-public class RepositoryChainDependencyResolver implements DependencyToComponentResolver {
-    private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryChainDependencyResolver.class);
-
-    private final List<ModuleComponentRepository> repositories = new ArrayList<ModuleComponentRepository>();
-    private final List<String> repositoryNames = new ArrayList<String>();
-    private final VersionedComponentChooser versionedComponentChooser;
-    private final Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> metaDataFactory;
-
-    public RepositoryChainDependencyResolver(VersionedComponentChooser versionedComponentChooser, Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> metaDataFactory) {
-        this.versionedComponentChooser = versionedComponentChooser;
-        this.metaDataFactory = metaDataFactory;
-    }
-
-    public void add(ModuleComponentRepository repository) {
-        repositories.add(repository);
-        repositoryNames.add(repository.getName());
-    }
-
-    public void resolve(DependencyMetaData dependency, BuildableComponentResolveResult result) {
-        ModuleVersionSelector requested = dependency.getRequested();
-        LOGGER.debug("Attempting to resolve {} using repositories {}", requested, repositoryNames);
-        ModuleComponentIdentifier moduleComponentIdentifier = new DefaultModuleComponentIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
-        ModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
-
-        List<Throwable> errors = new ArrayList<Throwable>();
-
-        List<ComponentMetaDataResolveState> resolveStates = new ArrayList<ComponentMetaDataResolveState>();
-        for (ModuleComponentRepository repository : repositories) {
-            resolveStates.add(new ComponentMetaDataResolveState(dependency, moduleComponentIdentifier, repository, versionedComponentChooser));
-        }
-
-        final RepositoryChainModuleResolution latestResolved = findBestMatch(resolveStates, errors);
-        if (latestResolved != null) {
-            LOGGER.debug("Using {} from {}", latestResolved.module.getId(), latestResolved.repository);
-            for (Throwable error : errors) {
-                LOGGER.debug("Discarding resolve failure.", error);
-            }
-
-            result.resolved(metaDataFactory.transform(latestResolved));
-            return;
-        }
-        if (!errors.isEmpty()) {
-            result.failed(new ModuleVersionResolveException(moduleComponentIdentifier, errors));
-        } else {
-            for (ComponentMetaDataResolveState resolveState : resolveStates) {
-                resolveState.applyTo(result);
-            }
-            result.notFound(moduleVersionIdentifier);
-        }
-    }
-
-    private RepositoryChainModuleResolution findBestMatch(List<ComponentMetaDataResolveState> resolveStates, Collection<Throwable> failures) {
-        LinkedList<ComponentMetaDataResolveState> queue = new LinkedList<ComponentMetaDataResolveState>();
-        queue.addAll(resolveStates);
-
-        LinkedList<ComponentMetaDataResolveState> missing = new LinkedList<ComponentMetaDataResolveState>();
-
-        // A first pass to do local resolves only
-        RepositoryChainModuleResolution best = findBestMatch(queue, failures, missing);
-        if (best != null) {
-            return best;
-        }
-
-        // Nothing found - do a second pass
-        queue.addAll(missing);
-        missing.clear();
-        return findBestMatch(queue, failures, missing);
-    }
-
-    private RepositoryChainModuleResolution findBestMatch(LinkedList<ComponentMetaDataResolveState> queue, Collection<Throwable> failures, Collection<ComponentMetaDataResolveState> missing) {
-        RepositoryChainModuleResolution best = null;
-        while (!queue.isEmpty()) {
-            ComponentMetaDataResolveState request = queue.removeFirst();
-            BuildableModuleComponentMetaDataResolveResult metaDataResolveResult;
-            try {
-                metaDataResolveResult = request.resolve();
-            } catch (Throwable t) {
-                failures.add(t);
-                continue;
-            }
-            switch (metaDataResolveResult.getState()) {
-                case Failed:
-                    failures.add(metaDataResolveResult.getFailure());
-                    break;
-                case Missing:
-                    // Queue this up for checking again later
-                    if (request.canMakeFurtherAttempts()) {
-                        missing.add(request);
-                    }
-                    break;
-                case Resolved:
-                    RepositoryChainModuleResolution moduleResolution = new RepositoryChainModuleResolution(request.repository, metaDataResolveResult.getMetaData());
-                    if (!metaDataResolveResult.getMetaData().isGenerated()) {
-                        return moduleResolution;
-                    }
-                    best = best != null ? best : moduleResolution;
-                    break;
-                default:
-                    throw new IllegalStateException("Unexpected state for resolution: " + metaDataResolveResult.getState());
-            }
-        }
-
-        return best;
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyToComponentIdResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyToComponentIdResolver.java
new file mode 100644
index 0000000..9fd8d95
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyToComponentIdResolver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme;
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
+import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
+import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
+import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
+
+public class RepositoryChainDependencyToComponentIdResolver implements DependencyToComponentIdResolver {
+    private final VersionSelectorScheme versionSelectorScheme;
+    private final DynamicVersionResolver dynamicRevisionResolver;
+
+    public RepositoryChainDependencyToComponentIdResolver(VersionSelectorScheme versionSelectorScheme, VersionedComponentChooser componentChooser, Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> metaDataFactory) {
+        this.versionSelectorScheme = versionSelectorScheme;
+        this.dynamicRevisionResolver = new DynamicVersionResolver(componentChooser, metaDataFactory);
+    }
+
+    public void add(ModuleComponentRepository repository) {
+        dynamicRevisionResolver.add(repository);
+    }
+
+    public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
+        ModuleVersionSelector requested = dependency.getRequested();
+        if (versionSelectorScheme.parseSelector(requested.getVersion()).isDynamic()) {
+            dynamicRevisionResolver.resolve(dependency, result);
+        } else {
+            DefaultModuleComponentIdentifier id = new DefaultModuleComponentIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
+            DefaultModuleVersionIdentifier mvId = new DefaultModuleVersionIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
+            result.resolved(id, mvId);
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
index 023ac47..67f52ea 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
 import org.gradle.api.internal.artifacts.ComponentMetadataProcessor;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
@@ -35,9 +35,9 @@ import org.gradle.internal.component.model.*;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
 import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
 import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
+import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
 import org.gradle.internal.resource.cached.CachedArtifactIndex;
 import org.gradle.util.BuildCommencedTimeProvider;
@@ -72,14 +72,13 @@ public class ResolveIvyFactory {
         this.versionComparator = versionComparator;
     }
 
-    public RepositoryChain create(ConfigurationInternal configuration,
+    public RepositoryChain create(ResolutionStrategyInternal resolutionStrategy,
                                   Collection<? extends ResolutionAwareRepository> repositories,
                                   ComponentMetadataProcessor metadataProcessor) {
         if (repositories.isEmpty()) {
             return new NoRepositoriesResolver();
         }
 
-        ResolutionStrategyInternal resolutionStrategy = configuration.getResolutionStrategy();
         ResolutionRules resolutionRules = resolutionStrategy.getResolutionRules();
         CachePolicy cachePolicy = resolutionStrategy.getCachePolicy();
 
@@ -124,7 +123,7 @@ public class ResolveIvyFactory {
     /**
      * Provides access to the top-level resolver chain for looking up parent modules when parsing module descriptor files.
      */
-    private static class ParentModuleLookupResolver implements RepositoryChain, DependencyToComponentResolver, ArtifactResolver {
+    private static class ParentModuleLookupResolver implements RepositoryChain, DependencyToComponentIdResolver, ComponentMetaDataResolver, ArtifactResolver {
         private final CacheLockingManager cacheLockingManager;
         private final UserResolverChain delegate;
 
@@ -137,26 +136,32 @@ public class ResolveIvyFactory {
             delegate.add(moduleComponentRepository);
         }
 
-        public ComponentMetaDataResolver getComponentMetaDataResolver() {
-            throw new UnsupportedOperationException();
-        }
-
         public DependencyToComponentIdResolver getComponentIdResolver() {
-            throw new UnsupportedOperationException();
+            return this;
         }
 
-        public ArtifactResolver getArtifactResolver() {
+        public ComponentMetaDataResolver getComponentResolver() {
             return this;
         }
 
-        public DependencyToComponentResolver getDependencyResolver() {
+        public ArtifactResolver getArtifactResolver() {
             return this;
         }
 
-        public void resolve(final DependencyMetaData dependency, final BuildableComponentResolveResult result) {
+        @Override
+        public void resolve(final DependencyMetaData dependency, final BuildableComponentIdResolveResult result) {
             cacheLockingManager.useCache(String.format("Resolve %s", dependency), new Runnable() {
                 public void run() {
-                    delegate.getDependencyResolver().resolve(dependency, result);
+                    delegate.getComponentIdResolver().resolve(dependency, result);
+                }
+            });
+        }
+
+        @Override
+        public void resolve(final ComponentIdentifier identifier, final ComponentOverrideMetadata componentOverrideMetadata, final BuildableComponentResolveResult result) {
+            cacheLockingManager.useCache(String.format("Resolve %s", identifier), new Runnable() {
+                public void run() {
+                    delegate.getComponentResolver().resolve(identifier, componentOverrideMetadata, result);
                 }
             });
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
index d74e251..07f7f90 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
@@ -107,7 +107,7 @@ public class StartParameterResolutionOverride {
             result.failed(new ModuleVersionResolveException(dependency.getRequested(), String.format("No cached version listing for %s available for offline mode.", dependency.getRequested())));
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
             result.failed(new ModuleVersionResolveException(moduleComponentIdentifier, String.format("No cached version of %s available for offline mode.", moduleComponentIdentifier.getDisplayName())));
         }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
index f912983..233fae6 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
@@ -24,34 +24,28 @@ import org.gradle.internal.component.external.model.ModuleComponentResolveMetaDa
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
 
 public class UserResolverChain implements RepositoryChain {
-    private final RepositoryChainDependencyResolver dependencyResolver;
-    private final RepositoryChainArtifactResolver artifactResolver = new RepositoryChainArtifactResolver();
-    private final RepositoryChainAdapter adapter;
-    private final DynamicVersionResolver dynamicVersionResolver;
+    private final RepositoryChainDependencyToComponentIdResolver componentIdResolver;
+    private final RepositoryChainComponentMetaDataResolver componentResolver;
+    private final RepositoryChainArtifactResolver artifactResolver;
     private final ComponentSelectionRulesInternal componentSelectionRules;
 
     public UserResolverChain(VersionSelectorScheme versionSelectorScheme, VersionComparator versionComparator, ComponentSelectionRulesInternal componentSelectionRules) {
         this.componentSelectionRules = componentSelectionRules;
-        DefaultVersionedComponentChooser componentChooser = new DefaultVersionedComponentChooser(versionComparator, versionSelectorScheme, componentSelectionRules);
+        VersionedComponentChooser componentChooser = new DefaultVersionedComponentChooser(versionComparator, versionSelectorScheme, componentSelectionRules);
         ModuleTransformer metaDataFactory = new ModuleTransformer();
-        dependencyResolver = new RepositoryChainDependencyResolver(componentChooser, metaDataFactory);
-        dynamicVersionResolver = new DynamicVersionResolver(componentChooser, metaDataFactory);
-        adapter = new RepositoryChainAdapter(dynamicVersionResolver, dependencyResolver, versionSelectorScheme);
+        componentIdResolver = new RepositoryChainDependencyToComponentIdResolver(versionSelectorScheme, componentChooser, metaDataFactory);
+        componentResolver = new RepositoryChainComponentMetaDataResolver(componentChooser, metaDataFactory);
+        artifactResolver = new RepositoryChainArtifactResolver();
     }
 
     public DependencyToComponentIdResolver getComponentIdResolver() {
-        return adapter;
+        return componentIdResolver;
     }
 
-    public ComponentMetaDataResolver getComponentMetaDataResolver() {
-        return adapter;
-    }
-
-    public DependencyToComponentResolver getDependencyResolver() {
-        return dependencyResolver;
+    public ComponentMetaDataResolver getComponentResolver() {
+        return componentResolver;
     }
 
     public ArtifactResolver getArtifactResolver() {
@@ -63,8 +57,8 @@ public class UserResolverChain implements RepositoryChain {
     }
 
     public void add(ModuleComponentRepository repository) {
-        dependencyResolver.add(repository);
-        dynamicVersionResolver.add(repository);
+        componentIdResolver.add(repository);
+        componentResolver.add(repository);
         artifactResolver.add(repository);
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionedComponentChooser.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionedComponentChooser.java
index a607dca..df91c9e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionedComponentChooser.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/VersionedComponentChooser.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
-import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.resolve.result.BuildableComponentSelectionResult;
 
 import java.util.Collection;
@@ -25,7 +25,7 @@ import java.util.Collection;
 public interface VersionedComponentChooser {
     ComponentResolveMetaData selectNewestComponent(ComponentResolveMetaData one, ComponentResolveMetaData two);
 
-    void selectNewestMatchingComponent(Collection<? extends ModuleComponentResolveState> versions, DependencyMetaData dependency, BuildableComponentSelectionResult result);
+    void selectNewestMatchingComponent(Collection<? extends ModuleComponentResolveState> versions, BuildableComponentSelectionResult result, ModuleVersionSelector requested);
 
     boolean isRejectedComponent(ModuleComponentIdentifier candidateIdentifier, MetadataProvider metadataProvider);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepository.java
index 13c8460..2ad30b0 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepository.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepository.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache;
 
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.component.model.ModuleSource;
 import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.*;
@@ -70,9 +71,9 @@ class InMemoryCachedModuleComponentRepository extends BaseModuleComponentReposit
             }
         }
 
-        public void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
             if(!metaDataCache.supplyMetaData(moduleComponentIdentifier, result)) {
-                super.resolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+                super.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
                 metaDataCache.newDependencyResult(moduleComponentIdentifier, result);
             }
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java
index 7edeff7..ba9b543 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DescriptorParseContext.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 
 public interface DescriptorParseContext {
-    LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, ArtifactType artifactType);
+    LocallyAvailableExternalResource getMetaDataArtifact(ModuleComponentIdentifier componentIdentifier, ArtifactType artifactType);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java
index 7552701..29d3286 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedDescriptorParseContext.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 
@@ -26,7 +26,7 @@ import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
  */
 public class DisconnectedDescriptorParseContext implements DescriptorParseContext {
 
-    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, ArtifactType artifactType) {
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleComponentIdentifier moduleComponentIdentifier, ArtifactType artifactType) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
index 87124b1..0efb676 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
@@ -20,8 +20,7 @@ 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.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 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;
@@ -29,6 +28,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.MavenVer
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme;
 import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.component.external.model.DefaultMavenModuleResolveMetaData;
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
 import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -80,7 +80,7 @@ public final class GradlePomModuleDescriptorParser extends AbstractModuleDescrip
         if (pomReader.hasParent()) {
             //Is there any other parent properties?
 
-            ModuleVersionIdentifier parentId = DefaultModuleVersionIdentifier.newId(
+            ModuleComponentIdentifier parentId = DefaultModuleComponentIdentifier.newId(
                     pomReader.getParentGroupId(),
                     pomReader.getParentArtifactId(),
                     pomReader.getParentVersion());
@@ -108,7 +108,7 @@ public final class GradlePomModuleDescriptorParser extends AbstractModuleDescrip
                         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));
+                PomReader relocatedModule = parseOtherPom(parserSettings, DefaultModuleComponentIdentifier.newId(relocation.getOrganisation(), relocation.getName(), relocation.getRevision()));
 
                 Collection<PomDependencyData> pomDependencyDataList = relocatedModule.getDependencies().values();
                 for(PomDependencyData pomDependencyData : pomDependencyDataList) {
@@ -194,7 +194,7 @@ public final class GradlePomModuleDescriptorParser extends AbstractModuleDescrip
      * @throws SAXException
      */
     private PomReader parseImportedPom(DescriptorParseContext parseContext, PomDependencyMgt pomDependencyMgt) throws IOException, SAXException {
-        ModuleVersionIdentifier importedId = DefaultModuleVersionIdentifier.newId(pomDependencyMgt.getGroupId(), pomDependencyMgt.getArtifactId(), pomDependencyMgt.getVersion());
+        ModuleComponentIdentifier importedId = DefaultModuleComponentIdentifier.newId(pomDependencyMgt.getGroupId(), pomDependencyMgt.getArtifactId(), pomDependencyMgt.getVersion());
         return parseOtherPom(parseContext, importedId);
     }
 
@@ -207,7 +207,7 @@ public final class GradlePomModuleDescriptorParser extends AbstractModuleDescrip
      * @throws IOException
      * @throws SAXException
      */
-    private PomReader parseOtherPom(DescriptorParseContext parseContext, ModuleVersionIdentifier parentId) throws IOException, SAXException {
+    private PomReader parseOtherPom(DescriptorParseContext parseContext, ModuleComponentIdentifier parentId) throws IOException, SAXException {
         LocallyAvailableExternalResource localResource = parseContext.getMetaDataArtifact(parentId, ArtifactType.MAVEN_POM);
         PomReader pomReader = new PomReader(localResource);
         GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(pomReader, gradleVersionSelectorScheme, mavenVersionSelectorScheme);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
index bfc5a6b..824ddd4 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
@@ -32,19 +32,19 @@ import org.apache.ivy.util.extendable.DefaultExtendableItem;
 import org.apache.ivy.util.url.URLHandlerRegistry;
 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.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.api.internal.artifacts.ivyservice.NamespaceId;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ResolverStrategy;
 import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.component.external.model.BuildableIvyModuleResolveMetaData;
 import org.gradle.internal.component.external.model.DefaultIvyModuleResolveMetaData;
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
 import org.gradle.internal.component.model.DefaultIvyArtifactName;
 import org.gradle.internal.component.model.IvyArtifactName;
 import org.gradle.internal.resource.ExternalResource;
-import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.gradle.internal.resource.ResourceNotFoundException;
+import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.gradle.internal.resource.transfer.UrlExternalResource;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.TextUtil;
@@ -730,7 +730,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         }
 
         protected ModuleDescriptor parseOtherIvyFile(String parentOrganisation, String parentModule, String parentRevision) throws IOException, ParseException, SAXException {
-            ModuleVersionIdentifier importedId = new DefaultModuleVersionIdentifier(parentOrganisation, parentModule, parentRevision);
+            ModuleComponentIdentifier importedId = DefaultModuleComponentIdentifier.newId(parentOrganisation, parentModule, parentRevision);
             LocallyAvailableExternalResource externalResource = parseContext.getMetaDataArtifact(importedId, ArtifactType.IVY_DESCRIPTOR);
 
             return parseModuleDescriptor(externalResource, externalResource.getLocalResource().getFile().toURI().toURL());
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java
index ac4d14d..e8db2ee 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/modulecache/CachedModuleDescriptorParseContext.java
@@ -16,9 +16,9 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.component.ArtifactType;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
+import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 
 /**
@@ -26,7 +26,7 @@ import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
  * Will only be used for parsing ivy.xml files, as pom files are converted before caching.
  */
 class CachedModuleDescriptorParseContext implements DescriptorParseContext {
-    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, ArtifactType artifactType) {
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleComponentIdentifier componentIdentifier, ArtifactType artifactType) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ComponentConverterSource.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ComponentConverterSource.java
new file mode 100644
index 0000000..e107e3c
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ComponentConverterSource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.ModuleInternal;
+
+import java.util.Set;
+
+public class ComponentConverterSource {
+    private final Set<? extends Configuration> configurations;
+    private final ModuleInternal module;
+
+    public ComponentConverterSource(Set<? extends Configuration> configurations, ModuleInternal module) {
+        this.configurations = configurations;
+        this.module = module;
+    }
+
+    public Set<? extends Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    public ModuleInternal getModule() {
+        return module;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/CompositeResolveLocalComponentFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/CompositeResolveLocalComponentFactory.java
new file mode 100644
index 0000000..b0184d5
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/CompositeResolveLocalComponentFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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 com.google.common.collect.Sets;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.internal.component.local.model.LocalComponentMetaData;
+
+import java.util.Set;
+
+public class CompositeResolveLocalComponentFactory implements LocalComponentFactory {
+    private final Set<LocalComponentFactory> factories;
+
+    public CompositeResolveLocalComponentFactory(LocalComponentFactory... factories) {
+        this.factories = Sets.newHashSet(factories);
+    }
+
+    public void addFactory(LocalComponentFactory factory) {
+        factories.add(factory);
+    }
+
+    @Override
+    public boolean canConvert(Object source) {
+        return true;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public LocalComponentMetaData convert(Object context) {
+        for (LocalComponentFactory factory : factories) {
+            if (factory.canConvert(context)) {
+                return factory.convert(context);
+            }
+        }
+        throw new IllegalArgumentException("Unable to find a local converter factory for type "+context.getClass());
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java
index 875715d..9d235a4 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverter.java
@@ -16,38 +16,13 @@
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
 
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.internal.component.model.DefaultIvyArtifactName;
-import org.gradle.internal.component.model.IvyArtifactName;
 import org.gradle.internal.component.local.model.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) {
-        ModuleVersionIdentifier id = metaData.getId();
         for (Configuration configuration : configurations) {
-            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
-                IvyArtifactName ivyArtifact = createIvyArtifact(publishArtifact, id);
-                metaData.addArtifact(configuration.getName(), ivyArtifact, publishArtifact.getFile());
-            }
-        }
-    }
-
-    public IvyArtifactName createIvyArtifact(PublishArtifact publishArtifact, ModuleVersionIdentifier moduleVersionIdentifier) {
-        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 = moduleVersionIdentifier.getName();
+            metaData.addArtifacts(configuration.getName(), configuration.getArtifacts());
         }
-        return new DefaultIvyArtifactName(name, publishArtifact.getType(), publishArtifact.getExtension(), extraAttributes);
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
index bc4ab0b..a393a87 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
@@ -19,7 +19,7 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
 import org.gradle.internal.component.local.model.MutableLocalComponentMetaData;
 
-import java.util.Arrays;
+import java.util.Set;
 
 public class DefaultConfigurationsToModuleDescriptorConverter implements ConfigurationsToModuleDescriptorConverter {
     public void addConfigurations(MutableLocalComponentMetaData metaData, Iterable<? extends Configuration> configurations) {
@@ -29,8 +29,8 @@ public class DefaultConfigurationsToModuleDescriptorConverter implements Configu
     }
 
     private void addConfiguration(MutableLocalComponentMetaData metaData, Configuration configuration) {
-        String[] superConfigs = Configurations.getNames(configuration.getExtendsFrom()).toArray(new String[configuration.getExtendsFrom().size()]);
-        Arrays.sort(superConfigs);
-        metaData.addConfiguration(configuration.getName(), configuration.isVisible(), configuration.getDescription(), superConfigs, configuration.isTransitive());
+        Set<String> hierarchy = Configurations.getNames(configuration.getHierarchy());
+        Set<String> extendsFrom = Configurations.getNames(configuration.getExtendsFrom());
+        metaData.addConfiguration(configuration.getName(), configuration.getDescription(), extendsFrom, hierarchy, configuration.isVisible(), configuration.isTransitive());
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java
index 491ed9b..6202573 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactory.java
@@ -16,12 +16,13 @@
 
 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.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ModuleInternal;
 import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
 import org.gradle.internal.component.local.model.DefaultLocalComponentMetaData;
@@ -30,9 +31,6 @@ import org.gradle.internal.component.local.model.MutableLocalComponentMetaData;
 import java.util.Set;
 
 public class ResolveLocalComponentFactory implements LocalComponentFactory {
-    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
-    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
-
     private final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
     private final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
     private final ComponentIdentifierFactory componentIdentifierFactory;
@@ -48,15 +46,30 @@ public class ResolveLocalComponentFactory implements LocalComponentFactory {
         this.configurationsToArtifactsConverter = configurationsToArtifactsConverter;
     }
 
-    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 = new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module), module.getStatus(), null);
-        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+    @Override
+    public boolean canConvert(Object source) {
+        return source instanceof ComponentConverterSource || source instanceof Configuration;
+    }
+
+    @Override
+    public MutableLocalComponentMetaData convert(Object source) {
+        Set<? extends Configuration> contexts;
+        ModuleInternal module;
+        if (source instanceof Configuration) {
+            contexts = ((Configuration) source).getAll();
+            module = ((ConfigurationInternal)source).getModule();
+        } else {
+            contexts = ((ComponentConverterSource)source).getConfigurations();
+            module = ((ComponentConverterSource)source).getModule();
+        }
+        assert contexts.size() > 0 : "No configurations found for module: " + module.getName() + ". Configure them or apply a plugin that does it.";
         ComponentIdentifier componentIdentifier = componentIdentifierFactory.createComponentIdentifier(module);
-        DefaultLocalComponentMetaData metaData = new DefaultLocalComponentMetaData(moduleDescriptor, componentIdentifier);
-        configurationsToModuleDescriptorConverter.addConfigurations(metaData, configurations);
-        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(metaData, configurations);
-        configurationsToArtifactsConverter.addArtifacts(metaData, configurations);
+        ModuleVersionIdentifier moduleVersionIdentifier = DefaultModuleVersionIdentifier.newId(module);
+        DefaultLocalComponentMetaData metaData = new DefaultLocalComponentMetaData(moduleVersionIdentifier, componentIdentifier, module.getStatus());
+        configurationsToModuleDescriptorConverter.addConfigurations(metaData, contexts);
+        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(metaData, contexts);
+        configurationsToArtifactsConverter.addArtifacts(metaData, contexts);
         return metaData;
     }
+
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
index 1f108e8..099a721 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
@@ -16,18 +16,14 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.DependencyArtifact;
 import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.util.WrapUtil;
+import org.gradle.internal.component.model.DefaultIvyArtifactName;
+import org.gradle.internal.component.model.IvyArtifactName;
+import org.gradle.util.CollectionUtils;
 
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.Set;
 
 public abstract class AbstractIvyDependencyDescriptorFactory implements IvyDependencyDescriptorFactory {
@@ -37,41 +33,27 @@ public abstract class AbstractIvyDependencyDescriptorFactory implements IvyDepen
         this.excludeRuleConverter = excludeRuleConverter;
     }
 
-    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
-                                                       DefaultDependencyDescriptor dependencyDescriptor) {
-        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
-        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
-        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
+    private String getExtension(DependencyArtifact artifact) {
+        return artifact.getExtension() != null ? artifact.getExtension() : artifact.getType();
     }
 
-    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
-                              DefaultDependencyDescriptor dependencyDescriptor) {
-        for (DependencyArtifact artifact : artifacts) {
-            DefaultDependencyArtifactDescriptor artifactDescriptor;
-            try {
-                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
-                        artifact.getType(),
-                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
-                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
-                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
-                                artifact.getClassifier()) : null);
-            } catch (MalformedURLException e) {
-                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
-            }
-            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
+    protected org.apache.ivy.core.module.descriptor.ExcludeRule[] convertExcludeRules(String configuration, Set<ExcludeRule> excludeRules) {
+        org.apache.ivy.core.module.descriptor.ExcludeRule[] ivyExcludeRules = new org.apache.ivy.core.module.descriptor.ExcludeRule[excludeRules.size()];
+        int i = 0;
+        for (ExcludeRule excludeRule : excludeRules) {
+            org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule = excludeRuleConverter.createExcludeRule(configuration, excludeRule);
+            ivyExcludeRules[i] = ivyExcludeRule;
+            i++;
         }
+        return ivyExcludeRules;
     }
 
-    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
-                                            DefaultDependencyDescriptor dependencyDescriptor) {
-        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
-    }
-
-    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
-                             DefaultDependencyDescriptor dependencyDescriptor) {
-        for (ExcludeRule excludeRule : excludeRules) {
-            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
-                    excludeRule));
-        }
+    protected Set<IvyArtifactName> convertArtifacts(Set<DependencyArtifact> dependencyArtifacts) {
+        return CollectionUtils.collect(dependencyArtifacts, new Transformer<IvyArtifactName, DependencyArtifact>() {
+            @Override
+            public IvyArtifactName transform(DependencyArtifact dependencyArtifact) {
+                return new DefaultIvyArtifactName(dependencyArtifact.getName(), dependencyArtifact.getType(), getExtension(dependencyArtifact), dependencyArtifact.getClassifier());
+            }
+        });
     }
 }
\ No newline at end of file
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
index 8d314f9..285ffe4 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
@@ -42,7 +42,7 @@ public class DefaultDependenciesToModuleDescriptorConverter implements Dependenc
     private void addDependencies(MutableLocalComponentMetaData metaData, Collection<? extends Configuration> configurations) {
         for (Configuration configuration : configurations) {
             for (ModuleDependency dependency : configuration.getDependencies().withType(ModuleDependency.class)) {
-                metaData.addDependency(dependencyDescriptorFactory.createDependencyDescriptor(configuration.getName(), metaData.getModuleDescriptor(), dependency));
+                metaData.addDependency(dependencyDescriptorFactory.createDependencyDescriptor(configuration.getName(), dependency));
             }
         }
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
index 7f222dd..f97e2e9 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.internal.component.model.DependencyMetaData;
@@ -31,9 +30,9 @@ public class DefaultDependencyDescriptorFactory implements DependencyDescriptorF
         this.dependencyDescriptorFactories = WrapUtil.toList(dependencyDescriptorFactories);
     }
 
-    public DependencyMetaData createDependencyDescriptor(String configuration, ModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+    public DependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency) {
         IvyDependencyDescriptorFactory factoryInternal = findFactoryForDependency(dependency);
-        return factoryInternal.createDependencyDescriptor(configuration, dependency, moduleDescriptor);
+        return factoryInternal.createDependencyDescriptor(configuration, dependency);
     }
 
     private IvyDependencyDescriptorFactory findFactoryForDependency(ModuleDependency dependency) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
index 56c3d82..1ae6084 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
@@ -15,10 +15,9 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.internal.component.model.DependencyMetaData;
 
 public interface DependencyDescriptorFactory {
-    DependencyMetaData createDependencyDescriptor(String configuration, ModuleDescriptor moduleDescriptor, ModuleDependency dependency);
+    DependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
index df8bd3b..8c8c3fd 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
@@ -15,42 +15,41 @@
  */
 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.ExternalModuleDependency;
 import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.internal.component.local.model.DslOriginDependencyMetaDataWrapper;
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
-import org.gradle.internal.component.model.DefaultDependencyMetaData;
-import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.local.model.DslOriginDependencyMetaDataWrapper;
+import org.gradle.internal.component.model.LocalComponentDependencyMetaData;
 
 public class ExternalModuleIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
     public ExternalModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
         super(excludeRuleConverter);
     }
 
-    private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return IvyUtil.createModuleRevisionId(dependency);
-    }
+    public DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency) {
+        ExternalModuleDependency externalModuleDependency = (ExternalModuleDependency) dependency;
+        boolean force = externalModuleDependency.isForce();
+        boolean changing = externalModuleDependency.isChanging();
+        boolean transitive = externalModuleDependency.isTransitive();
+
+        ModuleVersionSelector requested = new DefaultModuleVersionSelector(nullToEmpty(dependency.getGroup()), nullToEmpty(dependency.getName()), nullToEmpty(dependency.getVersion()));
+        ModuleComponentSelector selector = DefaultModuleComponentSelector.newSelector(requested);
 
-    public DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
-        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
-        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(
-                parent,
-                moduleRevisionId,
-                getExternalModuleDependency(dependency).isForce(),
-                getExternalModuleDependency(dependency).isChanging(),
-                getExternalModuleDependency(dependency).isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
-        DependencyMetaData dependencyMetaData = new DefaultDependencyMetaData(dependencyDescriptor);
+        LocalComponentDependencyMetaData dependencyMetaData = new LocalComponentDependencyMetaData(
+                selector, requested, configuration, dependency.getConfiguration(),
+                convertArtifacts(dependency.getArtifacts()),
+                convertExcludeRules(configuration, dependency.getExcludeRules()),
+                force, changing, transitive);
         return new DslOriginDependencyMetaDataWrapper(dependencyMetaData, dependency);
     }
 
-    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
-        return (ExternalModuleDependency) dependency;
+    private String nullToEmpty(String input) {
+        return input == null ? "" : input;
     }
 
     public boolean canConvert(ModuleDependency dependency) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java
index 2f51b47..13609c5 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java
@@ -15,12 +15,11 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
 
 public interface IvyDependencyDescriptorFactory {
-    DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor moduleDescriptor);
+    DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency);
 
     boolean canConvert(ModuleDependency dependency);
 }
\ No newline at end of file
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
index 0bbf6cb..2a429ea 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
@@ -15,42 +15,50 @@
  */
 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.Module;
 import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 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;
-import org.gradle.internal.component.local.model.DefaultProjectDependencyMetaData;
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector;
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaDataWrapper;
+import org.gradle.internal.component.model.LocalComponentDependencyMetaData;
 
 public class ProjectIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
     public ProjectIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
         super(excludeRuleConverter);
     }
 
-    public DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
+    public DslOriginDependencyMetaData createDependencyDescriptor(String configuration, ModuleDependency dependency) {
         ProjectDependencyInternal projectDependency = (ProjectDependencyInternal) dependency;
         projectDependency.beforeResolved();
-        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
-        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent, moduleRevisionId, false, false, dependency.isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
-        DefaultProjectDependencyMetaData projectDependencyMetaData = new DefaultProjectDependencyMetaData(dependencyDescriptor, projectDependency.getDependencyProject().getPath());
-        return new DslOriginDependencyMetaDataWrapper(projectDependencyMetaData, projectDependency);
+        ((ConfigurationInternal) projectDependency.getProjectConfiguration()).triggerWhenEmptyActionsIfNecessary();
+        Module module = getProjectModule(dependency);
+
+        // TODO:DAZ What are the GAV values for the dependency here?
+        ModuleVersionSelector requested = new DefaultModuleVersionSelector(module.getGroup(), module.getName(), module.getVersion());
+        ComponentSelector selector = DefaultProjectComponentSelector.newSelector(projectDependency.getDependencyProject().getPath());
+
+        LocalComponentDependencyMetaData dependencyMetaData = new LocalComponentDependencyMetaData(
+                selector, requested, configuration, dependency.getConfiguration(),
+                convertArtifacts(dependency.getArtifacts()),
+                convertExcludeRules(configuration, dependency.getExcludeRules()),
+                false, false, dependency.isTransitive());
+        return new DslOriginDependencyMetaDataWrapper(dependencyMetaData, dependency);
     }
 
     public boolean canConvert(ModuleDependency dependency) {
         return dependency instanceof ProjectDependency;
     }
 
-    private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+    private Module getProjectModule(ModuleDependency dependency) {
         ProjectDependency projectDependency = (ProjectDependency) dependency;
-        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
-        return IvyUtil.createModuleRevisionId(module);
+        return ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java
index b86f1f9..e4e623e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectComponentRegistry.java
@@ -16,9 +16,10 @@
 package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 
 import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
-import org.gradle.internal.component.local.model.LocalComponentMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ComponentConverterSource;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.ProjectRegistry;
+import org.gradle.internal.component.local.model.LocalComponentMetaData;
 
 public class DefaultProjectComponentRegistry implements ProjectComponentRegistry {
     private final LocalComponentFactory localComponentFactory;
@@ -31,6 +32,9 @@ public class DefaultProjectComponentRegistry implements ProjectComponentRegistry
 
     public LocalComponentMetaData getProject(String projectPath) {
         ProjectInternal project = projectRegistry.getProject(projectPath);
-        return localComponentFactory.convert(project.getConfigurations(), project.getModule());
+        if (project == null) {
+            return null;
+        }
+        return localComponentFactory.convert(new ComponentConverterSource(project.getConfigurations(), project.getModule()));
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java
index 0a4de6c..aca8cc3 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectArtifactResolver.java
@@ -16,17 +16,19 @@
 package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 
 import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+import org.gradle.api.internal.component.ArtifactType;
+import org.gradle.internal.component.local.model.LocalComponentArtifactIdentifier;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
 import org.gradle.internal.component.model.ComponentUsage;
 import org.gradle.internal.component.model.ModuleSource;
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-import org.gradle.internal.component.local.model.LocalArtifactMetaData;
-import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
 import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
 
+import java.io.File;
 import java.util.Set;
 
 public class ProjectArtifactResolver implements ArtifactResolver {
@@ -44,7 +46,7 @@ public class ProjectArtifactResolver implements ArtifactResolver {
     }
 
     public void resolveModuleArtifacts(ComponentResolveMetaData component, ComponentUsage usage, BuildableArtifactSetResolveResult result) {
-        if (isProjectModule(component.getComponentId())) {
+        if (isProjectModule(component.getComponentId()) || isLibrary(component.getComponentId())) {
             String configurationName = usage.getConfigurationName();
             Set<ComponentArtifactMetaData> artifacts = component.getConfiguration(configurationName).getArtifacts();
             result.resolved(artifacts);
@@ -55,9 +57,10 @@ public class ProjectArtifactResolver implements ArtifactResolver {
 
     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());
+            LocalComponentArtifactIdentifier id = (LocalComponentArtifactIdentifier) artifact.getId();
+            File localArtifactFile = id.getFile();
+            if (localArtifactFile != null) {
+                result.resolved(localArtifactFile);
             } else {
                 result.notFound(artifact.getId());
             }
@@ -69,4 +72,7 @@ public class ProjectArtifactResolver implements ArtifactResolver {
     private boolean isProjectModule(ComponentIdentifier componentId) {
         return componentId instanceof ProjectComponentIdentifier;
     }
+    private boolean isLibrary(ComponentIdentifier componentId) {
+        return componentId instanceof LibraryComponentIdentifier;
+    }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
index d2478a9..8ccd888 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
@@ -15,42 +15,66 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 
-import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolveContext;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentSelector;
-import org.gradle.api.internal.artifacts.ModuleInternal;
 import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector;
 import org.gradle.internal.component.local.model.LocalComponentMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.ModuleToComponentResolver;
+import org.gradle.internal.resolve.resolver.ResolveContextToComponentResolver;
 import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
 
-import java.util.Set;
-
-public class ProjectDependencyResolver implements DependencyToComponentIdResolver, ModuleToComponentResolver {
+public class ProjectDependencyResolver implements DependencyToComponentIdResolver, ResolveContextToComponentResolver, ComponentMetaDataResolver {
     private final ProjectComponentRegistry projectComponentRegistry;
-    private final DependencyToComponentIdResolver delegate;
+    private final DependencyToComponentIdResolver delegateIdResolver;
+    private final ComponentMetaDataResolver delegateComponentResolver;
     private final LocalComponentFactory localComponentFactory;
 
-    public ProjectDependencyResolver(ProjectComponentRegistry projectComponentRegistry, LocalComponentFactory localComponentFactory, DependencyToComponentIdResolver delegate) {
+    public ProjectDependencyResolver(ProjectComponentRegistry projectComponentRegistry, LocalComponentFactory localComponentFactory, DependencyToComponentIdResolver delegateIdResolver, ComponentMetaDataResolver delegateComponentResolver) {
         this.projectComponentRegistry = projectComponentRegistry;
-        this.delegate = delegate;
+        this.delegateIdResolver = delegateIdResolver;
+        this.delegateComponentResolver = delegateComponentResolver;
         this.localComponentFactory = localComponentFactory;
     }
 
     public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
         if (dependency.getSelector() instanceof ProjectComponentSelector) {
             ProjectComponentSelector selector = (ProjectComponentSelector) dependency.getSelector();
-            LocalComponentMetaData componentMetaData = projectComponentRegistry.getProject(selector.getProjectPath());
-            result.resolved(componentMetaData.toResolveMetaData());
+            String projectPath = selector.getProjectPath();
+            LocalComponentMetaData componentMetaData = projectComponentRegistry.getProject(projectPath);
+            if (componentMetaData == null) {
+                result.failed(new ModuleVersionResolveException(selector, "project '" + projectPath + "' not found."));
+            } else {
+                result.resolved(componentMetaData.toResolveMetaData());
+            }
+        } else {
+            delegateIdResolver.resolve(dependency, result);
+        }
+    }
+
+    public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
+        if (identifier instanceof ProjectComponentIdentifier) {
+            String projectPath = ((ProjectComponentIdentifier) identifier).getProjectPath();
+            LocalComponentMetaData componentMetaData = projectComponentRegistry.getProject(projectPath);
+            if (componentMetaData == null) {
+                result.failed(new ModuleVersionResolveException(new DefaultProjectComponentSelector(projectPath), "project '" + projectPath + "' not found."));
+            } else {
+                result.resolved(componentMetaData.toResolveMetaData());
+            }
         } else {
-            delegate.resolve(dependency, result);
+            delegateComponentResolver.resolve(identifier, componentOverrideMetadata, result);
         }
     }
 
-    public void resolve(ModuleInternal module, Set<? extends Configuration> configurations, BuildableComponentResolveResult result) {
-        LocalComponentMetaData componentMetaData = localComponentFactory.convert(configurations, module);
+    public void resolve(ResolveContext resolveContext, BuildableComponentResolveResult result) {
+        LocalComponentMetaData componentMetaData = localComponentFactory.convert(resolveContext);
         result.resolved(componentMetaData.toResolveMetaData());
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
index 99cf4ae..d4d2db8 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
@@ -62,7 +62,7 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
     /**
      * Sets the validator to invoke prior to each mutation.
      */
-    public void beforeChange(MutationValidator validator) {
+    public void setMutationValidator(MutationValidator validator) {
         this.mutationValidator = validator;
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRules.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRules.java
index 251b253..0509fda 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRules.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRules.java
@@ -28,7 +28,7 @@ import org.gradle.api.artifacts.ModuleIdentifier;
 import org.gradle.api.artifacts.ivy.IvyModuleDescriptor;
 import org.gradle.api.internal.artifacts.ComponentSelectionRulesInternal;
 import org.gradle.api.internal.artifacts.configurations.MutationValidator;
-import org.gradle.api.internal.notations.ModuleIdentiferNotationConverter;
+import org.gradle.api.internal.notations.ModuleIdentifierNotationConverter;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.internal.rules.*;
@@ -62,14 +62,14 @@ public class DefaultComponentSelectionRules implements ComponentSelectionRulesIn
     /**
      * Sets the validator to invoke prior to each mutation.
      */
-    public void beforeChange(MutationValidator mutationValidator) {
+    public void setMutationValidator(MutationValidator mutationValidator) {
         this.mutationValidator = mutationValidator;
     }
 
     private static NotationParser<Object, ModuleIdentifier> createModuleIdentifierNotationParser() {
         return NotationParserBuilder
                 .toType(ModuleIdentifier.class)
-                .converter(new ModuleIdentiferNotationConverter())
+                .converter(new ModuleIdentifierNotationConverter())
                 .toComposite();
     }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutions.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutions.java
deleted file mode 100644
index fe90db4..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutions.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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 com.google.common.base.Objects;
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.artifacts.component.ComponentSelector;
-import org.gradle.api.artifacts.component.ModuleComponentSelector;
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
-import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
-import org.gradle.api.internal.artifacts.configurations.MutationValidator;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultDependencyResolveDetails;
-import org.gradle.api.internal.notations.ModuleIdentiferNotationConverter;
-import org.gradle.internal.Actions;
-import org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier;
-import org.gradle.internal.exceptions.DiagnosticsVisitor;
-import org.gradle.internal.typeconversion.*;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class DefaultDependencySubstitutions implements DependencySubstitutionsInternal {
-    private final Set<Action<? super DependencySubstitution<? super ComponentSelector>>> substitutionRules;
-    private final NotationParser<Object, ModuleIdentifier> moduleIdentifierNotationParser;
-    private final NotationParser<Object, ProjectComponentIdentifier> projectIdentifierNotationParser;
-
-    private MutationValidator mutationValidator = MutationValidator.IGNORE;
-
-    public DefaultDependencySubstitutions() {
-        this(new LinkedHashSet<Action<? super DependencySubstitution<? super ComponentSelector>>>());
-    }
-
-    DefaultDependencySubstitutions(Set<Action<? super DependencySubstitution<? super ComponentSelector>>> substitutionRules) {
-        this.substitutionRules = substitutionRules;
-        this.moduleIdentifierNotationParser = createModuleIdentifierNotationParser();
-        this.projectIdentifierNotationParser = createProjectIdentifierNotationParser();
-    }
-
-    @Override
-    public Action<DependencySubstitution<ComponentSelector>> getDependencySubstitutionRule() {
-        return Actions.composite(substitutionRules);
-    }
-
-    private void addRule(Action<? super DependencySubstitution<? super ComponentSelector>> rule) {
-        mutationValidator.validateMutation(MutationValidator.MutationType.STRATEGY);
-        substitutionRules.add(rule);
-    }
-
-    @Override
-    public DependencySubstitutions all(Action<? super DependencySubstitution<? super ComponentSelector>> rule) {
-        addRule(rule);
-        return this;
-    }
-
-    @Override
-    public DependencySubstitutions allWithDependencyResolveDetails(Action<? super DependencyResolveDetails> rule) {
-        addRule(new DependencyResolveDetailsWrapperAction(rule));
-        return this;
-    }
-
-    @Override
-    public DependencySubstitutions all(Closure<?> action) {
-        return all(ClosureBackedAction.of(action));
-    }
-
-    @Override
-    public DependencySubstitutions eachModule(Action<? super ModuleDependencySubstitution> rule) {
-        return all(new TypeFilteringDependencySubstitutionAction<ModuleDependencySubstitution>(ModuleDependencySubstitution.class, rule));
-    }
-
-    @Override
-    public DependencySubstitutions eachModule(Closure<?> rule) {
-        return eachModule(ClosureBackedAction.of(rule));
-    }
-
-    @Override
-    public DependencySubstitutions withModule(Object id, Action<? super ModuleDependencySubstitution> rule) {
-        ModuleIdentifier moduleId = moduleIdentifierNotationParser.parseNotation(id);
-        return all(new ModuleIdFilteringDependencySubstitutionAction(moduleId, rule));
-    }
-
-    @Override
-    public DependencySubstitutions withModule(Object id, Closure<?> action) {
-        return withModule(id, ClosureBackedAction.of(action));
-    }
-
-    @Override
-    public DependencySubstitutions eachProject(Action<? super ProjectDependencySubstitution> rule) {
-        return all(new TypeFilteringDependencySubstitutionAction<ProjectDependencySubstitution>(ProjectDependencySubstitution.class, rule));
-    }
-
-    @Override
-    public DependencySubstitutions eachProject(Closure<?> rule) {
-        return eachProject(ClosureBackedAction.of(rule));
-    }
-
-    @Override
-    public DependencySubstitutions withProject(Object id, Action<? super ProjectDependencySubstitution> rule) {
-        ProjectComponentIdentifier componentId = projectIdentifierNotationParser.parseNotation(id);
-        return all(new ProjectIdFilteringDependencySubstitutionAction(componentId, rule));
-    }
-
-    @Override
-    public DependencySubstitutions withProject(Object id, Closure<?> rule) {
-        return withProject(id, ClosureBackedAction.of(rule));
-    }
-
-    @Override
-    public void beforeChange(MutationValidator validator) {
-        mutationValidator = validator;
-    }
-
-    @Override
-    public DependencySubstitutionsInternal copy() {
-        return new DefaultDependencySubstitutions(new LinkedHashSet<Action<? super DependencySubstitution<? super ComponentSelector>>>(substitutionRules));
-    }
-
-    private static NotationParser<Object, ModuleIdentifier> createModuleIdentifierNotationParser() {
-        return NotationParserBuilder
-                .toType(ModuleIdentifier.class)
-                .converter(new ModuleIdentiferNotationConverter())
-                .converter(ModuleIdentifierMapNotationConverter.getInstance())
-                .toComposite();
-    }
-
-    private static class ModuleIdentifierMapNotationConverter extends MapNotationConverter<ModuleIdentifier> {
-
-        private final static ModuleIdentifierMapNotationConverter INSTANCE = new ModuleIdentifierMapNotationConverter();
-
-        public static ModuleIdentifierMapNotationConverter getInstance() {
-            return INSTANCE;
-        }
-
-        @Override
-        public void describe(DiagnosticsVisitor visitor) {
-            visitor.example("Maps, e.g. [group: 'org.gradle', name:'gradle-core'].");
-        }
-
-        protected ModuleIdentifier parseMap(@MapKey("group") String group, @MapKey("name") String name) {
-            return DefaultModuleIdentifier.newId(group, name);
-        }
-    }
-
-    private static NotationParser<Object, ProjectComponentIdentifier> createProjectIdentifierNotationParser() {
-        return NotationParserBuilder
-                .toType(ProjectComponentIdentifier.class)
-                .fromCharSequence(new ProjectPathConverter())
-                .fromType(Project.class, new ProjectConverter())
-                .toComposite();
-    }
-
-    private static class ProjectPathConverter implements NotationConverter<String, ProjectComponentIdentifier> {
-        @Override
-        public void describe(DiagnosticsVisitor visitor) {
-            visitor.example("Project paths, e.g. ':api'.");
-        }
-
-        @Override
-        public void convert(String notation, NotationConvertResult<? super ProjectComponentIdentifier> result) throws TypeConversionException {
-            result.converted(DefaultProjectComponentIdentifier.newId(notation));
-        }
-    }
-
-    private static class ProjectConverter implements NotationConverter<Project, ProjectComponentIdentifier> {
-
-        @Override
-        public void describe(DiagnosticsVisitor visitor) {
-            visitor.example("Project objects, e.g. project(':api').");
-        }
-
-        @Override
-        public void convert(Project notation, NotationConvertResult<? super ProjectComponentIdentifier> result) throws TypeConversionException {
-            result.converted(DefaultProjectComponentIdentifier.newId(notation.getPath()));
-        }
-    }
-
-    private static class ModuleIdFilteringDependencySubstitutionAction implements Action<DependencySubstitution<ComponentSelector>> {
-        private final Action<? super ModuleDependencySubstitution> delegate;
-        private final ModuleIdentifier id;
-
-        public ModuleIdFilteringDependencySubstitutionAction(ModuleIdentifier id, Action<? super ModuleDependencySubstitution> delegate) {
-            this.id = id;
-            this.delegate = delegate;
-        }
-
-        @Override
-        public void execute(DependencySubstitution substitution) {
-            ComponentSelector requested = substitution.getRequested();
-            if (requested instanceof ModuleComponentSelector) {
-                ModuleComponentSelector requestedModule = (ModuleComponentSelector) requested;
-                if (Objects.equal(requestedModule.getGroup(), id.getGroup())
-                        && Objects.equal(requestedModule.getModule(), id.getName())) {
-                    delegate.execute((ModuleDependencySubstitution) substitution);
-                }
-            }
-        }
-    }
-
-    private static class ProjectIdFilteringDependencySubstitutionAction implements Action<DependencySubstitution<ComponentSelector>> {
-        private final Action<? super ProjectDependencySubstitution> delegate;
-        private final ProjectComponentIdentifier id;
-
-        public ProjectIdFilteringDependencySubstitutionAction(ProjectComponentIdentifier id, Action<? super ProjectDependencySubstitution> delegate) {
-            this.id = id;
-            this.delegate = delegate;
-        }
-
-        @Override
-        public void execute(DependencySubstitution substitution) {
-            ComponentSelector requested = substitution.getRequested();
-            if (requested.matchesStrictly(id)) {
-                delegate.execute((ProjectDependencySubstitution) substitution);
-            }
-        }
-    }
-
-    private static class TypeFilteringDependencySubstitutionAction<T extends DependencySubstitution<?>> implements Action<DependencySubstitution<ComponentSelector>> {
-        private final Class<T> type;
-        private final Action<? super T> delegate;
-
-        public TypeFilteringDependencySubstitutionAction(Class<T> type, Action<? super T> delegate) {
-            this.type = type;
-            this.delegate = delegate;
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public void execute(DependencySubstitution<ComponentSelector> substitution) {
-            if (type.isAssignableFrom(substitution.getClass())) {
-                delegate.execute((T) substitution);
-            }
-        }
-    }
-
-    private static class DependencyResolveDetailsWrapperAction implements Action<DependencySubstitution<? extends ComponentSelector>> {
-        private final Action<? super DependencyResolveDetails> delegate;
-
-        public DependencyResolveDetailsWrapperAction(Action<? super DependencyResolveDetails> delegate) {
-            this.delegate = delegate;
-        }
-
-        @Override
-        public void execute(DependencySubstitution<? extends ComponentSelector> substitution) {
-            DefaultDependencyResolveDetails details = new DefaultDependencyResolveDetails((DependencySubstitutionInternal<?>) substitution);
-            delegate.execute(details);
-        }
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
index 009d778..b6410af 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
@@ -19,11 +19,12 @@ package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.artifacts.cache.ResolutionRules;
-import org.gradle.api.artifacts.component.ComponentSelector;
 import org.gradle.api.internal.artifacts.ComponentSelectionRulesInternal;
 import org.gradle.api.internal.artifacts.configurations.MutationValidator;
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
 import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
+import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DefaultDependencySubstitutions;
+import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DependencySubstitutionsInternal;
 import org.gradle.internal.Actions;
 import org.gradle.internal.typeconversion.NormalizedTimeUnit;
 import org.gradle.internal.typeconversion.TimeUnitsParser;
@@ -46,6 +47,9 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
     private final DependencySubstitutionsInternal dependencySubstitutions;
     private MutationValidator mutationValidator = MutationValidator.IGNORE;
 
+    private boolean assumeFluidDependencies;
+    private static final String ASSUME_FLUID_DEPENDENCIES = "org.gradle.resolution.assumeFluidDependencies";
+
     public DefaultResolutionStrategy() {
         this(new DefaultCachePolicy(), new DefaultDependencySubstitutions());
     }
@@ -53,14 +57,17 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
     DefaultResolutionStrategy(DefaultCachePolicy cachePolicy, DependencySubstitutionsInternal dependencySubstitutions) {
         this.cachePolicy = cachePolicy;
         this.dependencySubstitutions = dependencySubstitutions;
+
+        // This is only used for testing purposes so we can test handling of fluid dependencies without adding dependency substituion rule
+        assumeFluidDependencies = Boolean.getBoolean(ASSUME_FLUID_DEPENDENCIES);
     }
 
     @Override
-    public void beforeChange(MutationValidator validator) {
+    public void setMutationValidator(MutationValidator validator) {
         mutationValidator = validator;
-        cachePolicy.beforeChange(validator);
-        componentSelectionRules.beforeChange(validator);
-        dependencySubstitutions.beforeChange(validator);
+        cachePolicy.setMutationValidator(validator);
+        componentSelectionRules.setMutationValidator(validator);
+        dependencySubstitutions.setMutationValidator(validator);
     }
 
     public Set<ModuleVersionSelector> getForcedModules() {
@@ -94,11 +101,20 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
         return this;
     }
 
-    public Action<DependencySubstitution<ComponentSelector>> getDependencySubstitutionRule() {
-        Collection<Action<DependencySubstitution<ComponentSelector>>> allRules = flattenElements(new ModuleForcingResolveRule(forcedModules), dependencySubstitutions.getDependencySubstitutionRule());
+    public Action<DependencySubstitution> getDependencySubstitutionRule() {
+        Collection<Action<DependencySubstitution>> allRules = flattenElements(new ModuleForcingResolveRule(forcedModules), dependencySubstitutions.getDependencySubstitutionRule());
         return Actions.composite(allRules);
     }
 
+    public void assumeFluidDependencies() {
+        assumeFluidDependencies = true;
+    }
+
+    public boolean resolveGraphToDetermineTaskDependencies() {
+        return assumeFluidDependencies || dependencySubstitutions.hasDependencySubstitutionRules();
+    }
+
+
     public DefaultResolutionStrategy setForcedModules(Object ... moduleVersionSelectorNotations) {
         mutationValidator.validateMutation(STRATEGY);
         Set<ModuleVersionSelector> modules = ModuleVersionSelectorParsers.multiParser().parseNotation(moduleVersionSelectorNotations);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DependencySubstitutionsInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DependencySubstitutionsInternal.java
deleted file mode 100644
index 126bc9a..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DependencySubstitutionsInternal.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.Action;
-import org.gradle.api.artifacts.DependencyResolveDetails;
-import org.gradle.api.artifacts.DependencySubstitution;
-import org.gradle.api.artifacts.DependencySubstitutions;
-import org.gradle.api.artifacts.component.ComponentSelector;
-import org.gradle.api.internal.artifacts.configurations.MutationValidator;
-
-public interface DependencySubstitutionsInternal extends DependencySubstitutions {
-    Action<DependencySubstitution<ComponentSelector>> getDependencySubstitutionRule();
-
-    DependencySubstitutions allWithDependencyResolveDetails(Action<? super DependencyResolveDetails> rule);
-
-    void beforeChange(MutationValidator validator);
-
-    DependencySubstitutionsInternal copy();
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
index 68ed461..06468b6 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
@@ -19,17 +19,17 @@ package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.ModuleIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.artifacts.component.ModuleComponentSelector;
 import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
 import org.gradle.api.internal.artifacts.DependencySubstitutionInternal;
-import org.gradle.api.internal.artifacts.ModuleDependencySubstitutionInternal;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
-public class ModuleForcingResolveRule implements Action<DependencySubstitutionInternal<? extends ComponentSelector>> {
+public class ModuleForcingResolveRule implements Action<DependencySubstitutionInternal> {
 
     private final Map<ModuleIdentifier, String> forcedModules;
 
@@ -45,13 +45,13 @@ public class ModuleForcingResolveRule implements Action<DependencySubstitutionIn
     }
 
     @Override
-    public void execute(DependencySubstitutionInternal<? extends ComponentSelector> details) {
+    public void execute(DependencySubstitutionInternal details) {
         if (forcedModules == null) {
             return;
         }
         ModuleIdentifier key = new DefaultModuleIdentifier(details.getOldRequested().getGroup(), details.getOldRequested().getName());
-        if (forcedModules.containsKey(key) && details instanceof ModuleDependencySubstitutionInternal) {
-            ((ModuleDependencySubstitutionInternal) details).useVersion(forcedModules.get(key), VersionSelectionReasons.FORCED);
+        if (forcedModules.containsKey(key) && details.getRequested() instanceof ModuleComponentSelector) {
+            details.useTarget(DefaultModuleComponentSelector.newSelector(key.getGroup(), key.getName(), forcedModules.get(key)), VersionSelectionReasons.FORCED);
         }
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
index 293dba4..61e0fb9 100755
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
@@ -17,15 +17,19 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
 import org.apache.ivy.Ivy;
 import org.gradle.api.Action;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolveContext;
 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.GlobalDependencyResolutionRules;
+import org.gradle.api.internal.artifacts.ResolveContextInternal;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
 import org.gradle.api.internal.artifacts.ivyservice.*;
 import org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DependencySubstitutionResolver;
 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;
@@ -34,13 +38,12 @@ import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
 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.resolutionstrategy.DefaultResolutionStrategy;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.StrictConflictResolution;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.ConflictHandler;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.DefaultConflictHandler;
-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.oldresult.*;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.DefaultResolvedProjectConfigurationResultBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResultBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
@@ -50,6 +53,7 @@ import org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.StoreSet
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.internal.cache.BinaryStore;
 import org.gradle.api.internal.cache.Store;
+import org.gradle.internal.Factory;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
@@ -68,10 +72,11 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
     private final IvyContextManager ivyContextManager;
     private final ResolutionResultsStoreFactory storeFactory;
     private final VersionComparator versionComparator;
+    private final boolean buildProjectDependencies;
 
     public DefaultDependencyResolver(ResolveIvyFactory ivyFactory, LocalComponentFactory localComponentFactory, DependencyDescriptorFactory dependencyDescriptorFactory,
                                      ProjectComponentRegistry projectComponentRegistry, CacheLockingManager cacheLockingManager, IvyContextManager ivyContextManager,
-                                     ResolutionResultsStoreFactory storeFactory, VersionComparator versionComparator) {
+                                     ResolutionResultsStoreFactory storeFactory, VersionComparator versionComparator, boolean buildProjectDependencies) {
         this.ivyFactory = ivyFactory;
         this.localComponentFactory = localComponentFactory;
         this.dependencyDescriptorFactory = dependencyDescriptorFactory;
@@ -80,21 +85,31 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         this.ivyContextManager = ivyContextManager;
         this.storeFactory = storeFactory;
         this.versionComparator = versionComparator;
+        this.buildProjectDependencies = buildProjectDependencies;
     }
 
-    public void resolve(final ConfigurationInternal configuration,
+    public void resolve(final ResolveContext resolveContext,
                         final List<? extends ResolutionAwareRepository> repositories,
                         final GlobalDependencyResolutionRules metadataHandler,
                         final ResolverResults results) throws ResolveException {
-        LOGGER.debug("Resolving {}", configuration);
+        LOGGER.debug("Resolving {}", resolveContext);
         ivyContextManager.withIvy(new Action<Ivy>() {
             public void execute(Ivy ivy) {
-                RepositoryChain repositoryChain = ivyFactory.create(configuration, repositories, metadataHandler.getComponentMetadataProcessor());
-
-                ComponentMetaDataResolver metaDataResolver = new ClientModuleResolver(repositoryChain.getComponentMetaDataResolver(), dependencyDescriptorFactory);
+                ResolutionStrategyInternal resolutionStrategy;
+                if (resolveContext instanceof ConfigurationInternal) {
+                    resolutionStrategy =((ConfigurationInternal) resolveContext).getResolutionStrategy();
+                } else {
+                    resolutionStrategy = new DefaultResolutionStrategy();
+                }
+                RepositoryChain repositoryChain = ivyFactory.create(resolutionStrategy, repositories, metadataHandler.getComponentMetadataProcessor());
 
-                ProjectDependencyResolver projectDependencyResolver = new ProjectDependencyResolver(projectComponentRegistry, localComponentFactory, repositoryChain.getComponentIdResolver());
-                ResolutionStrategyInternal resolutionStrategy = configuration.getResolutionStrategy();
+                ComponentMetaDataResolver metaDataResolver = new ClientModuleResolver(repositoryChain.getComponentResolver(), dependencyDescriptorFactory);
+                ProjectDependencyResolver projectDependencyResolver;
+                if (resolveContext instanceof ResolveContextInternal) {
+                    projectDependencyResolver = ((ResolveContextInternal) resolveContext).newProjectDependencyResolver(projectComponentRegistry, localComponentFactory, repositoryChain.getComponentIdResolver(), metaDataResolver);
+                } else {
+                    projectDependencyResolver = new ProjectDependencyResolver(projectComponentRegistry, localComponentFactory, repositoryChain.getComponentIdResolver(), metaDataResolver);
+                }
                 DependencyToComponentIdResolver idResolver = new DependencySubstitutionResolver(projectDependencyResolver, resolutionStrategy.getDependencySubstitutionRule());
 
                 ArtifactResolver artifactResolver = createArtifactResolver(repositoryChain);
@@ -108,7 +123,7 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
                 conflictResolver = new VersionSelectionReasonResolver(conflictResolver);
                 ConflictHandler conflictHandler = new DefaultConflictHandler(conflictResolver, metadataHandler.getModuleMetadataProcessor().getModuleReplacements());
 
-                DependencyGraphBuilder builder = new DependencyGraphBuilder(idResolver, metaDataResolver, projectDependencyResolver, artifactResolver, conflictHandler, new DefaultDependencyToConfigurationResolver());
+                DependencyGraphBuilder builder = new DependencyGraphBuilder(idResolver, projectDependencyResolver, projectDependencyResolver, artifactResolver, conflictHandler, new DefaultDependencyToConfigurationResolver());
 
                 StoreSet stores = storeFactory.createStoreSet();
 
@@ -120,15 +135,35 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
                 Store<TransientConfigurationResults> oldModelCache = stores.newModelStore();
                 TransientConfigurationResultsBuilder oldTransientModelBuilder = new TransientConfigurationResultsBuilder(oldModelStore, oldModelCache);
                 DefaultResolvedConfigurationBuilder oldModelBuilder = new DefaultResolvedConfigurationBuilder(oldTransientModelBuilder);
-                ResolvedProjectConfigurationResultBuilder projectModelBuilder = new DefaultResolvedProjectConfigurationResultBuilder();
+                DefaultResolvedArtifactsBuilder artifactsBuilder = new DefaultResolvedArtifactsBuilder();
+                ResolvedProjectConfigurationResultBuilder projectModelBuilder = new DefaultResolvedProjectConfigurationResultBuilder(buildProjectDependencies);
+
+                // Resolve the dependency graph
+                builder.resolve(resolveContext, newModelBuilder, oldModelBuilder, artifactsBuilder, projectModelBuilder);
+                results.resolved(newModelBuilder.complete(), projectModelBuilder.complete());
 
-                builder.resolve(configuration, newModelBuilder, oldModelBuilder, projectModelBuilder);
-                DefaultLenientConfiguration result = new DefaultLenientConfiguration(configuration, oldModelBuilder, cacheLockingManager);
-                results.resolved(new DefaultResolvedConfiguration(result), newModelBuilder.complete(), projectModelBuilder.complete());
+                ResolvedGraphResults graphResults = oldModelBuilder.complete();
+                results.retainState(graphResults, artifactsBuilder, oldTransientModelBuilder);
             }
         });
     }
 
+    public void resolveArtifacts(final ResolveContext resolveContext,
+                                 final List<? extends ResolutionAwareRepository> repositories,
+                                 final GlobalDependencyResolutionRules metadataHandler,
+                                 final ResolverResults results) throws ResolveException {
+        // TODO:DAZ Should not be holding onto all of this state
+        ResolvedGraphResults graphResults = results.getGraphResults();
+
+        ResolvedArtifactResults artifactResults = results.getArtifactsBuilder().resolve();
+
+        Factory<TransientConfigurationResults> transientConfigurationResultsFactory = new TransientConfigurationResultsLoader(results.getTransientConfigurationResultsBuilder(), graphResults, artifactResults);
+
+        DefaultLenientConfiguration result = new DefaultLenientConfiguration(
+            (Configuration) resolveContext, cacheLockingManager, graphResults, artifactResults, transientConfigurationResultsFactory);
+        results.withResolvedConfiguration(new DefaultResolvedConfiguration(result));
+    }
+
     private ArtifactResolver createArtifactResolver(RepositoryChain repositoryChain) {
         ArtifactResolver artifactResolver = repositoryChain.getArtifactResolver();
         artifactResolver = new ProjectArtifactResolver(artifactResolver);
@@ -136,4 +171,5 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         artifactResolver = new ErrorHandlingArtifactResolver(artifactResolver);
         return artifactResolver;
     }
+
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java
index 16bde3c..a71d755 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyToConfigurationResolver.java
@@ -16,14 +16,11 @@
 
 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.internal.component.model.ComponentResolveMetaData;
 import org.gradle.internal.component.model.ConfigurationMetaData;
 import org.gradle.internal.component.model.DependencyMetaData;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -31,32 +28,24 @@ public class DefaultDependencyToConfigurationResolver implements DependencyToCon
     // TODO - don't pass in 'from' configuration - the dependency should have whatever context it needs
     public Set<ConfigurationMetaData> resolveTargetConfigurations(DependencyMetaData dependencyMetaData, ConfigurationMetaData fromConfiguration, ComponentResolveMetaData 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()) {
+        for (String config : dependencyMetaData.getModuleConfigurations()) {
             if (config.equals("*") || config.equals("%")) {
-                collectTargetConfiguration(dependencyDescriptor, fromConfiguration, fromConfiguration.getName(), targetDescriptor, targetConfigurationNames);
+                collectTargetConfiguration(dependencyMetaData, fromConfiguration, fromConfiguration.getName(), targetComponent, targetConfigurationNames);
             } else if (fromConfiguration.getHierarchy().contains(config)) {
-                collectTargetConfiguration(dependencyDescriptor, fromConfiguration, config, targetDescriptor, targetConfigurationNames);
+                collectTargetConfiguration(dependencyMetaData, fromConfiguration, config, targetComponent, 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) {
+    private void collectTargetConfiguration(DependencyMetaData dependencyDescriptor, ConfigurationMetaData fromConfiguration, String mappingRhs, ComponentResolveMetaData targetModule, Collection<String> targetConfigs) {
         String[] dependencyConfigurations = dependencyDescriptor.getDependencyConfigurations(mappingRhs, fromConfiguration.getName());
         for (String target : dependencyConfigurations) {
             String candidate = target;
@@ -72,10 +61,20 @@ public class DefaultDependencyToConfigurationResolver implements DependencyToCon
                 }
             }
             if (candidate.equals("*")) {
-                Collections.addAll(targetConfigs, targetModule.getPublicConfigurationsNames());
+                for (String configName : targetModule.getConfigurationNames()) {
+                    if (targetModule.getConfiguration(configName).isVisible()) {
+                        targetConfigs.add(configName);
+                    }
+                }
+                continue;
+            }
+            if (targetModule.getConfiguration(candidate) != null) {
+                targetConfigs.add(candidate);
                 continue;
             }
-            targetConfigs.add(candidate);
+            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(),
+                    target, targetModule.getId()));
         }
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilter.java
index 2785f6e..7df8475 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilter.java
@@ -192,6 +192,11 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
         public boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact) {
             return true;
         }
+
+        @Override
+        public boolean acceptsAllArtifacts() {
+            return true;
+        }
     }
 
     private static abstract class CompositeSpec extends DefaultModuleResolutionFilter {
@@ -272,16 +277,16 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
 
                 if (anyArtifact) {
                     if (!anyOrganisation && !anyModule) {
-                        excludeSpecs.add(new ModuleIdSpec(moduleId.getOrganisation(), moduleId.getName()));
+                        excludeSpecs.add(new ModuleIdExcludeSpec(moduleId.getOrganisation(), moduleId.getName()));
                     } else if (!anyModule) {
-                        excludeSpecs.add(new ModuleNameSpec(moduleId.getName()));
+                        excludeSpecs.add(new ModuleNameExcludeSpec(moduleId.getName()));
                     } else if (!anyOrganisation) {
-                        excludeSpecs.add(new GroupNameSpec(moduleId.getOrganisation()));
+                        excludeSpecs.add(new GroupNameExcludeSpec(moduleId.getOrganisation()));
                     } else {
                         excludeSpecs.add(new ExcludeAllModulesSpec());
                     }
                 } else {
-                    excludeSpecs.add(new ArtifactSpec(rule));
+                    excludeSpecs.add(new ArtifactExcludeSpec(rule));
                 }
             }
         }
@@ -328,6 +333,16 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             return true;
         }
 
+        public boolean acceptsAllArtifacts() {
+            for (DefaultModuleResolutionFilter spec : excludeSpecs) {
+                if (!spec.acceptsAllArtifacts()) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
         @Override
         protected DefaultModuleResolutionFilter doUnion(DefaultModuleResolutionFilter other) {
             if (!(other instanceof ExcludeRuleBackedSpec)) {
@@ -364,62 +379,72 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             return new ExcludeRuleBackedSpec(merged);
         }
 
+        // Add filters to the list that will accept modules that are accepted by either of the candidate filters.
         private void intersect(DefaultModuleResolutionFilter spec1, DefaultModuleResolutionFilter spec2, List<DefaultModuleResolutionFilter> merged) {
-            if (spec1 instanceof ArtifactSpec) {
+            if (spec1 instanceof ExcludeAllModulesSpec) {
+                merged.add(spec2);
+            } else if (spec2 instanceof ExcludeAllModulesSpec) {
+                merged.add(spec1);
+            } else if (spec1 instanceof ArtifactExcludeSpec) {
                 merged.add(spec1);
-            } else if (spec2 instanceof ArtifactSpec) {
+            } else if (spec2 instanceof ArtifactExcludeSpec) {
                 merged.add(spec2);
-            } else if (spec1 instanceof GroupNameSpec) {
-                intersect((GroupNameSpec) spec1, spec2, merged);
-            } else if (spec2 instanceof GroupNameSpec) {
-                intersect((GroupNameSpec) spec2, spec1, merged);
-            } else if (spec1 instanceof ModuleNameSpec) {
-                intersect((ModuleNameSpec) spec1, spec2, merged);
-            } else if (spec2 instanceof ModuleNameSpec) {
-                intersect((ModuleNameSpec) spec2, spec1, merged);
-            } else if ((spec1 instanceof ModuleIdSpec) && (spec2 instanceof ModuleIdSpec)) {
-                ModuleIdSpec moduleSpec1 = (ModuleIdSpec) spec1;
-                ModuleIdSpec moduleSpec2 = (ModuleIdSpec) spec2;
+            } else if (spec1 instanceof GroupNameExcludeSpec) {
+                intersect((GroupNameExcludeSpec) spec1, spec2, merged);
+            } else if (spec2 instanceof GroupNameExcludeSpec) {
+                intersect((GroupNameExcludeSpec) spec2, spec1, merged);
+            } else if (spec1 instanceof ModuleNameExcludeSpec) {
+                intersect((ModuleNameExcludeSpec) spec1, spec2, merged);
+            } else if (spec2 instanceof ModuleNameExcludeSpec) {
+                intersect((ModuleNameExcludeSpec) spec2, spec1, merged);
+            } else if ((spec1 instanceof ModuleIdExcludeSpec) && (spec2 instanceof ModuleIdExcludeSpec)) {
+                ModuleIdExcludeSpec moduleSpec1 = (ModuleIdExcludeSpec) spec1;
+                ModuleIdExcludeSpec moduleSpec2 = (ModuleIdExcludeSpec) spec2;
                 if (moduleSpec1.moduleId.equals(moduleSpec2.moduleId)) {
                     merged.add(moduleSpec1);
                 }
             } else {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException(String.format("Cannot calculate intersection of exclude rules: %s, %s", spec1, spec2));
             }
         }
 
-        private void intersect(GroupNameSpec spec1, DefaultModuleResolutionFilter spec2, List<DefaultModuleResolutionFilter> merged) {
-            if (spec2 instanceof GroupNameSpec) {
-                GroupNameSpec groupNameSpec = (GroupNameSpec) spec2;
-                if (spec1.group.equals(groupNameSpec.group)) {
+        private void intersect(GroupNameExcludeSpec spec1, DefaultModuleResolutionFilter spec2, List<DefaultModuleResolutionFilter> merged) {
+            if (spec2 instanceof GroupNameExcludeSpec) {
+                // Intersection of 2 group excludes does nothing unless excluded groups match
+                GroupNameExcludeSpec groupNameExcludeSpec = (GroupNameExcludeSpec) spec2;
+                if (spec1.group.equals(groupNameExcludeSpec.group)) {
                     merged.add(spec1);
                 }
-            } else if (spec2 instanceof ModuleNameSpec) {
-                ModuleNameSpec moduleNameSpec = (ModuleNameSpec) spec2;
-                merged.add(new ModuleIdSpec(spec1.group, moduleNameSpec.module));
-            } else if (spec2 instanceof ModuleIdSpec) {
-                ModuleIdSpec moduleIdSpec = (ModuleIdSpec) spec2;
-                if (moduleIdSpec.moduleId.getGroup().equals(spec1.group)) {
+            } else if (spec2 instanceof ModuleNameExcludeSpec) {
+                // Intersection of group & module name exclude only excludes module with matching group + name
+                ModuleNameExcludeSpec moduleNameExcludeSpec = (ModuleNameExcludeSpec) spec2;
+                merged.add(new ModuleIdExcludeSpec(spec1.group, moduleNameExcludeSpec.module));
+            } else if (spec2 instanceof ModuleIdExcludeSpec) {
+                // Intersection of group + module id exclude only excludes the module id if the excluded groups match
+                ModuleIdExcludeSpec moduleIdExcludeSpec = (ModuleIdExcludeSpec) spec2;
+                if (moduleIdExcludeSpec.moduleId.getGroup().equals(spec1.group)) {
                     merged.add(spec2);
                 }
             } else {
-                throw new UnsupportedOperationException();
-            }
+                throw new UnsupportedOperationException(String.format("Cannot calculate intersection of exclude rules: %s, %s", spec1, spec2));
+             }
         }
 
-        private void intersect(ModuleNameSpec spec1, DefaultModuleResolutionFilter spec2, List<DefaultModuleResolutionFilter> merged) {
-            if (spec2 instanceof ModuleNameSpec) {
-                ModuleNameSpec moduleNameSpec = (ModuleNameSpec) spec2;
-                if (spec1.module.equals(moduleNameSpec.module)) {
+        private void intersect(ModuleNameExcludeSpec spec1, DefaultModuleResolutionFilter spec2, List<DefaultModuleResolutionFilter> merged) {
+            if (spec2 instanceof ModuleNameExcludeSpec) {
+                // Intersection of 2 module name excludes does nothing unless excluded module names match
+                ModuleNameExcludeSpec moduleNameExcludeSpec = (ModuleNameExcludeSpec) spec2;
+                if (spec1.module.equals(moduleNameExcludeSpec.module)) {
                     merged.add(spec1);
                 }
-            } else if (spec2 instanceof ModuleIdSpec) {
-                ModuleIdSpec moduleIdSpec = (ModuleIdSpec) spec2;
-                if (moduleIdSpec.moduleId.getName().equals(spec1.module)) {
+            } else if (spec2 instanceof ModuleIdExcludeSpec) {
+                // Intersection of module name & module id exclude only excludes module if the excluded module names match
+                ModuleIdExcludeSpec moduleIdExcludeSpec = (ModuleIdExcludeSpec) spec2;
+                if (moduleIdExcludeSpec.moduleId.getName().equals(spec1.module)) {
                     merged.add(spec2);
                 }
             } else {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException(String.format("Cannot calculate intersection of exclude rules: %s, %s", spec1, spec2));
             }
         }
     }
@@ -470,12 +495,26 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
 
             return false;
         }
+
+        public boolean acceptsAllArtifacts() {
+            for (DefaultModuleResolutionFilter spec : specs) {
+                if (spec.acceptsAllArtifacts()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
     }
 
-    private static class ModuleIdSpec extends DefaultModuleResolutionFilter {
+    /**
+     * A ModuleResolutionFilter that accepts any module that has a module id other than the one specified.
+     * Accepts all artifacts.
+     */
+    private static class ModuleIdExcludeSpec extends DefaultModuleResolutionFilter {
         private final ModuleIdentifier moduleId;
 
-        public ModuleIdSpec(String group, String name) {
+        public ModuleIdExcludeSpec(String group, String name) {
             this.moduleId = DefaultModuleIdentifier.newId(group, name);
         }
 
@@ -492,7 +531,7 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             if (o == null || o.getClass() != getClass()) {
                 return false;
             }
-            ModuleIdSpec other = (ModuleIdSpec) o;
+            ModuleIdExcludeSpec other = (ModuleIdExcludeSpec) o;
             return moduleId.equals(other.moduleId);
         }
 
@@ -503,8 +542,8 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
 
         @Override
         protected boolean doAcceptsSameModulesAs(DefaultModuleResolutionFilter other) {
-            ModuleIdSpec moduleIdSpec = (ModuleIdSpec) other;
-            return moduleId.equals(moduleIdSpec.moduleId);
+            ModuleIdExcludeSpec moduleIdExcludeSpec = (ModuleIdExcludeSpec) other;
+            return moduleId.equals(moduleIdExcludeSpec.moduleId);
         }
 
         public boolean acceptModule(ModuleIdentifier module) {
@@ -514,12 +553,20 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
         public boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact) {
             return true;
         }
+
+        public boolean acceptsAllArtifacts() {
+            return true;
+        }
     }
 
-    private static class ModuleNameSpec extends DefaultModuleResolutionFilter {
+    /**
+     * A ModuleResolutionFilter that accepts any module that has a name other than the one specified.
+     * Accepts all artifacts.
+     */
+    private static class ModuleNameExcludeSpec extends DefaultModuleResolutionFilter {
         private final String module;
 
-        private ModuleNameSpec(String module) {
+        private ModuleNameExcludeSpec(String module) {
             this.module = module;
         }
 
@@ -536,7 +583,7 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             if (o == null || o.getClass() != getClass()) {
                 return false;
             }
-            ModuleNameSpec other = (ModuleNameSpec) o;
+            ModuleNameExcludeSpec other = (ModuleNameExcludeSpec) o;
             return module.equals(other.module);
         }
 
@@ -547,8 +594,8 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
 
         @Override
         public boolean doAcceptsSameModulesAs(DefaultModuleResolutionFilter other) {
-            ModuleNameSpec moduleNameSpec = (ModuleNameSpec) other;
-            return module.equals(moduleNameSpec.module);
+            ModuleNameExcludeSpec moduleNameExcludeSpec = (ModuleNameExcludeSpec) other;
+            return module.equals(moduleNameExcludeSpec.module);
         }
 
         public boolean acceptModule(ModuleIdentifier element) {
@@ -558,12 +605,20 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
         public boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact) {
             return true;
         }
+
+        public boolean acceptsAllArtifacts() {
+            return true;
+        }
     }
 
-    private static class GroupNameSpec extends DefaultModuleResolutionFilter {
+    /**
+     * A ModuleResolutionFilter that accepts any module that has a group other than the one specified.
+     * Accepts all artifacts.
+     */
+    private static class GroupNameExcludeSpec extends DefaultModuleResolutionFilter {
         private final String group;
 
-        private GroupNameSpec(String group) {
+        private GroupNameExcludeSpec(String group) {
             this.group = group;
         }
 
@@ -580,7 +635,7 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             if (o == null || o.getClass() != getClass()) {
                 return false;
             }
-            GroupNameSpec other = (GroupNameSpec) o;
+            GroupNameExcludeSpec other = (GroupNameExcludeSpec) o;
             return group.equals(other.group);
         }
 
@@ -591,8 +646,8 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
 
         @Override
         public boolean doAcceptsSameModulesAs(DefaultModuleResolutionFilter other) {
-            GroupNameSpec groupNameSpec = (GroupNameSpec) other;
-            return group.equals(groupNameSpec.group);
+            GroupNameExcludeSpec groupNameExcludeSpec = (GroupNameExcludeSpec) other;
+            return group.equals(groupNameExcludeSpec.group);
         }
 
         public boolean acceptModule(ModuleIdentifier element) {
@@ -602,6 +657,10 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
         public boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact) {
             return true;
         }
+
+        public boolean acceptsAllArtifacts() {
+            return true;
+        }
     }
 
     private static class ExcludeAllModulesSpec extends DefaultModuleResolutionFilter {
@@ -632,8 +691,15 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
         public boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact) {
             return true;
         }
+
+        public boolean acceptsAllArtifacts() {
+            return true;
+        }
     }
 
+    /**
+     * A ModuleResolutionFilter that accepts any module/artifact that doesn't match the exclude rule.
+     */
     private static class ExcludeRuleSpec extends DefaultModuleResolutionFilter {
         private final ModuleIdentifier moduleId;
         private final IvyArtifactName ivyArtifactName;
@@ -697,17 +763,25 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             return true;
         }
 
+        public boolean acceptsAllArtifacts() {
+            return !isArtifactExclude;
+        }
+
         private boolean matches(String expression, String input) {
             return matcher.getMatcher(expression).matches(input);
         }
     }
 
-    private static class ArtifactSpec extends DefaultModuleResolutionFilter {
+    /**
+     * A ModuleResolutionFilter that accepts any artifact that doesn't match the exclude rule.
+     * Accepts all modules.
+     */
+    private static class ArtifactExcludeSpec extends DefaultModuleResolutionFilter {
         private final ModuleIdentifier moduleId;
         private final IvyArtifactName ivyArtifactName;
         private final PatternMatcher matcher;
 
-        private ArtifactSpec(ExcludeRule rule) {
+        private ArtifactExcludeSpec(ExcludeRule rule) {
             this.moduleId = DefaultModuleIdentifier.newId(rule.getId().getModuleId().getOrganisation(), rule.getId().getModuleId().getName());
             this.ivyArtifactName = new DefaultIvyArtifactName(rule.getId().getName(), rule.getId().getType(), rule.getId().getExt());
             this.matcher = rule.getMatcher();
@@ -726,7 +800,7 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
             if (o == null || o.getClass() != getClass()) {
                 return false;
             }
-            ArtifactSpec other = (ArtifactSpec) o;
+            ArtifactExcludeSpec other = (ArtifactExcludeSpec) o;
             return moduleId.equals(other.moduleId) && ivyArtifactName.equals(other.ivyArtifactName);
         }
 
@@ -757,6 +831,10 @@ public abstract class DefaultModuleResolutionFilter implements ModuleResolutionF
                     && matches(ivyArtifactName.getType(), artifact.getType()));
         }
 
+        public boolean acceptsAllArtifacts() {
+            return false;
+        }
+
         private boolean matches(String expression, String input) {
             return matcher.getMatcher(expression).matches(input);
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleResolutionFilter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleResolutionFilter.java
index 191ccdc..8c4b0ad 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleResolutionFilter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleResolutionFilter.java
@@ -29,16 +29,23 @@ public interface ModuleResolutionFilter {
     boolean acceptModule(ModuleIdentifier module);
 
     /**
+     * Determines if this filter accepts the same set of modules as the other.
+     *
+     * @return true if the filters accept the same set of modules. Returns false if they may not, or if it is unknown.
+     */
+    boolean acceptsSameModulesAs(ModuleResolutionFilter other);
+
+    /**
      * Should this artifact be included in the resolution result?
      */
     boolean acceptArtifact(ModuleIdentifier module, IvyArtifactName artifact);
 
     /**
-     * Determines if this filter accepts the same set of modules as the other.
+     * Does this filter accept all artifacts?
      *
-     * @return true if the filters accept the same set of modules. Returns false if they may not, or if it is unknown.
+     * @return true if this filter returns <code>true</code> for {@link #acceptArtifact} for any provided artifact.
      */
-    boolean acceptsSameModulesAs(ModuleResolutionFilter other);
+    boolean acceptsAllArtifacts();
 
     /**
      * Returns a filter that accepts the union of those module versions and artifacts that are accepted by this filter and the other.
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/AbstractArtifactSet.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/AbstractArtifactSet.java
new file mode 100644
index 0000000..a428029
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/AbstractArtifactSet.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.graph;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.ModuleResolutionFilter;
+import org.gradle.internal.Factory;
+import org.gradle.internal.component.model.ComponentArtifactIdentifier;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.IvyArtifactName;
+import org.gradle.internal.component.model.ModuleSource;
+import org.gradle.internal.resolve.resolver.ArtifactResolver;
+import org.gradle.internal.resolve.result.DefaultBuildableArtifactResolveResult;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class AbstractArtifactSet implements ArtifactSet {
+    private final ModuleVersionIdentifier moduleVersionIdentifier;
+    private final ModuleSource moduleSource;
+    private final ModuleResolutionFilter selector;
+    private final ArtifactResolver artifactResolver;
+    private final Map<ComponentArtifactIdentifier, ResolvedArtifact> allResolvedArtifacts;
+    private final long id;
+
+    public AbstractArtifactSet(ModuleVersionIdentifier ownerId, ModuleSource moduleSource, ModuleResolutionFilter selector, ArtifactResolver artifactResolver,
+                               Map<ComponentArtifactIdentifier, ResolvedArtifact> allResolvedArtifacts, long id) {
+        this.moduleVersionIdentifier = ownerId;
+        this.moduleSource = moduleSource;
+        this.selector = selector;
+        this.artifactResolver = artifactResolver;
+        this.allResolvedArtifacts = allResolvedArtifacts;
+        this.id = id;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public Set<ResolvedArtifact> getArtifacts() {
+        Set<ComponentArtifactMetaData> componentArtifacts = resolveComponentArtifacts();
+        Set<ResolvedArtifact> resolvedArtifacts = new LinkedHashSet<ResolvedArtifact>(componentArtifacts.size());
+        for (ComponentArtifactMetaData artifact : componentArtifacts) {
+            IvyArtifactName artifactName = artifact.getName();
+            if (!selector.acceptArtifact(moduleVersionIdentifier.getModule(), artifactName)) {
+                continue;
+            }
+
+            ResolvedArtifact resolvedArtifact = allResolvedArtifacts.get(artifact.getId());
+            if (resolvedArtifact == null) {
+                Factory<File> artifactSource = new LazyArtifactSource(artifact, moduleSource, artifactResolver);
+                resolvedArtifact = new DefaultResolvedArtifact(new DefaultResolvedModuleVersion(moduleVersionIdentifier), artifactName, artifactSource);
+                allResolvedArtifacts.put(artifact.getId(), resolvedArtifact);
+            }
+            resolvedArtifacts.add(resolvedArtifact);
+        }
+        return resolvedArtifacts;
+    }
+
+    protected ArtifactResolver getArtifactResolver() {
+        return artifactResolver;
+    }
+
+    protected abstract Set<ComponentArtifactMetaData> resolveComponentArtifacts();
+
+    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();
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ArtifactSet.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ArtifactSet.java
new file mode 100644
index 0000000..63a7b95
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ArtifactSet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.graph;
+
+import org.gradle.api.artifacts.ResolvedArtifact;
+
+import java.util.Set;
+
+public interface ArtifactSet {
+
+    long getId();
+
+    Set<ResolvedArtifact> getArtifacts();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ConfigurationArtifactSet.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ConfigurationArtifactSet.java
new file mode 100644
index 0000000..96371b6
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ConfigurationArtifactSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.graph;
+
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.ModuleResolutionFilter;
+import org.gradle.internal.component.model.*;
+import org.gradle.internal.resolve.resolver.ArtifactResolver;
+import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
+import org.gradle.internal.resolve.result.DefaultBuildableArtifactSetResolveResult;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An ArtifactSet that resolves the artifacts for a configuration.
+ */
+class ConfigurationArtifactSet extends AbstractArtifactSet {
+    private final Set<ComponentArtifactMetaData> artifacts;
+
+    public ConfigurationArtifactSet(ComponentResolveMetaData component, ResolvedConfigurationIdentifier configurationId, ModuleResolutionFilter selector,
+                                    ArtifactResolver artifactResolver, Map<ComponentArtifactIdentifier, ResolvedArtifact> allResolvedArtifacts,
+                                    long id) {
+        super(component.getId(), component.getSource(), selector, artifactResolver, allResolvedArtifacts, id);
+        this.artifacts = doResolve(component, configurationId);
+    }
+
+    private Set<ComponentArtifactMetaData> doResolve(ComponentResolveMetaData component, ResolvedConfigurationIdentifier configurationId) {
+        BuildableArtifactSetResolveResult result = new DefaultBuildableArtifactSetResolveResult();
+        getArtifactResolver().resolveModuleArtifacts(component, new DefaultComponentUsage(configurationId.getConfiguration()), result);
+        return result.getArtifacts();
+    }
+
+    @Override
+    protected Set<ComponentArtifactMetaData> resolveComponentArtifacts() {
+        return artifacts;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyArtifactSet.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyArtifactSet.java
new file mode 100644
index 0000000..fffdd7d
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyArtifactSet.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.graph;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultModuleResolutionFilter;
+import org.gradle.internal.component.model.ComponentArtifactIdentifier;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.ModuleSource;
+import org.gradle.internal.resolve.resolver.ArtifactResolver;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A set of artifacts that is defined by a dependency declaration.
+ */
+class DependencyArtifactSet extends AbstractArtifactSet {
+    private final Set<ComponentArtifactMetaData> artifacts;
+
+    public DependencyArtifactSet(ModuleVersionIdentifier ownerId, ModuleSource moduleSource, Set<ComponentArtifactMetaData> artifacts,
+                                 ArtifactResolver artifactResolver, Map<ComponentArtifactIdentifier, ResolvedArtifact> allResolvedArtifacts,
+                                 long id) {
+        super(ownerId, moduleSource, DefaultModuleResolutionFilter.all(), artifactResolver, allResolvedArtifacts, id);
+        this.artifacts = artifacts;
+    }
+
+    @Override
+    protected Set<ComponentArtifactMetaData> resolveComponentArtifacts() {
+        return artifacts;
+    }
+
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyGraphBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyGraphBuilder.java
index 007f2ad..27d7bfb 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyGraphBuilder.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/DependencyGraphBuilder.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import com.google.common.base.Joiner;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.artifacts.component.ComponentIdentifier;
@@ -23,12 +23,12 @@ 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.resolveengine.*;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.CandidateModule;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.ConflictHandler;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.ConflictResolutionResult;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.PotentialConflict;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedArtifactsBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedConfigurationBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResultBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.InternalDependencyResult;
@@ -41,7 +41,7 @@ import org.gradle.internal.resolve.ModuleVersionResolveException;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
-import org.gradle.internal.resolve.resolver.ModuleToComponentResolver;
+import org.gradle.internal.resolve.resolver.ResolveContextToComponentResolver;
 import org.gradle.internal.resolve.result.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,14 +52,14 @@ public class DependencyGraphBuilder {
     private static final Logger LOGGER = LoggerFactory.getLogger(DependencyGraphBuilder.class);
     private final DependencyToConfigurationResolver dependencyToConfigurationResolver;
     private final ConflictHandler conflictHandler;
-    private final ModuleToComponentResolver moduleResolver;
+    private final ResolveContextToComponentResolver moduleResolver;
     private final ArtifactResolver artifactResolver;
     private final DependencyToComponentIdResolver idResolver;
     private final ComponentMetaDataResolver metaDataResolver;
 
     public DependencyGraphBuilder(DependencyToComponentIdResolver idResolver,
                                   ComponentMetaDataResolver metaDataResolver,
-                                  ModuleToComponentResolver moduleResolver,
+                                  ResolveContextToComponentResolver moduleResolver,
                                   ArtifactResolver artifactResolver,
                                   ConflictHandler conflictHandler,
                                   DependencyToConfigurationResolver dependencyToConfigurationResolver) {
@@ -71,23 +71,24 @@ public class DependencyGraphBuilder {
         this.dependencyToConfigurationResolver = dependencyToConfigurationResolver;
     }
 
-    public void resolve(ConfigurationInternal configuration,
+    public void resolve(ResolveContext resolveContext,
                         ResolutionResultBuilder newModelBuilder,
                         ResolvedConfigurationBuilder oldModelBuilder,
+                        ResolvedArtifactsBuilder artifactsBuilder,
                         ResolvedProjectConfigurationResultBuilder projectModelBuilder) throws ResolveException {
-        DependencyGraphVisitor oldModelVisitor = new ResolvedConfigurationDependencyGraphVisitor(oldModelBuilder, artifactResolver);
+        DependencyGraphVisitor oldModelVisitor = new ResolvedConfigurationDependencyGraphVisitor(oldModelBuilder, artifactsBuilder, artifactResolver);
         DependencyGraphVisitor newModelVisitor = new ResolutionResultDependencyGraphVisitor(newModelBuilder);
         DependencyGraphVisitor projectModelVisitor = new ResolvedProjectConfigurationResultGraphVisitor(projectModelBuilder);
         DependencyGraphVisitor modelVisitor = new CompositeDependencyGraphVisitor(oldModelVisitor, newModelVisitor, projectModelVisitor);
 
-        resolveDependencyGraph(configuration, modelVisitor);
+        resolveDependencyGraph(resolveContext, modelVisitor);
     }
 
-    private void resolveDependencyGraph(ConfigurationInternal configuration, DependencyGraphVisitor modelVisitor) {
+    private void resolveDependencyGraph(ResolveContext resolveContext, DependencyGraphVisitor modelVisitor) {
         DefaultBuildableComponentResolveResult rootModule = new DefaultBuildableComponentResolveResult();
-        moduleResolver.resolve(configuration.getModule(), configuration.getAll(), rootModule);
+        moduleResolver.resolve(resolveContext, rootModule);
 
-        ResolveState resolveState = new ResolveState(rootModule, configuration.getName(), idResolver, metaDataResolver, dependencyToConfigurationResolver, artifactResolver);
+        ResolveState resolveState = new ResolveState(rootModule, resolveContext.getName(), idResolver, metaDataResolver, dependencyToConfigurationResolver);
         conflictHandler.registerResolver(new DirectDependencyForcingResolver(resolveState.root.moduleRevision));
 
         traverseGraph(resolveState, conflictHandler);
@@ -203,7 +204,6 @@ public class DependencyGraphBuilder {
         public final ModuleVersionSelectorResolveState selector;
 
         private final DependencyMetaData dependencyMetaData;
-        private final DependencyDescriptor dependencyDescriptor;
         private final ResolveState resolveState;
         private final ModuleResolutionFilter resolutionFilter;
         private final Set<ConfigurationNode> targetConfigurations = new LinkedHashSet<ConfigurationNode>();
@@ -212,7 +212,6 @@ public class DependencyGraphBuilder {
         public DependencyEdge(ConfigurationNode from, DependencyMetaData dependencyMetaData, ModuleResolutionFilter resolutionFilter, ResolveState resolveState) {
             this.from = from;
             this.dependencyMetaData = dependencyMetaData;
-            this.dependencyDescriptor = dependencyMetaData.getDescriptor();
             this.resolutionFilter = resolutionFilter;
             this.resolveState = resolveState;
             selector = resolveState.getSelector(dependencyMetaData);
@@ -220,7 +219,7 @@ public class DependencyGraphBuilder {
 
         @Override
         public String toString() {
-            return String.format("%s -> %s(%s)", from.toString(), dependencyMetaData.getRequested(), dependencyDescriptor);
+            return String.format("%s -> %s(%s)", from.toString(), dependencyMetaData.getRequested(), Joiner.on(',').join(dependencyMetaData.getModuleConfigurations()));
         }
 
         /**
@@ -282,8 +281,8 @@ public class DependencyGraphBuilder {
         }
 
         public ModuleResolutionFilter getSelector() {
-            String[] configurations = from.metaData.getHierarchy().toArray(new String[from.metaData.getHierarchy().size()]);
-            ModuleResolutionFilter selector = DefaultModuleResolutionFilter.excludeAny(dependencyDescriptor.getExcludeRules(configurations));
+            Set<String> hierarchy = from.metaData.getHierarchy();
+            ModuleResolutionFilter selector = DefaultModuleResolutionFilter.excludeAny(dependencyMetaData.getExcludeRules(hierarchy));
             return selector.intersect(resolutionFilter);
         }
 
@@ -309,7 +308,10 @@ public class DependencyGraphBuilder {
         }
 
         public ModuleDependency getModuleDependency() {
-            return ((DslOriginDependencyMetaData) dependencyMetaData).getSource();
+            if (dependencyMetaData instanceof DslOriginDependencyMetaData) {
+                return ((DslOriginDependencyMetaData) dependencyMetaData).getSource();
+            }
+            return null;
         }
 
         public Set<ComponentArtifactMetaData> getArtifacts(ConfigurationMetaData metaData1) {
@@ -328,17 +330,14 @@ public class DependencyGraphBuilder {
         private final DependencyToComponentIdResolver idResolver;
         private final ComponentMetaDataResolver metaDataResolver;
         private final DependencyToConfigurationResolver dependencyToConfigurationResolver;
-        private final ArtifactResolver artifactResolver;
         private final Set<ConfigurationNode> queued = new HashSet<ConfigurationNode>();
         private final LinkedList<ConfigurationNode> queue = new LinkedList<ConfigurationNode>();
 
         public ResolveState(ComponentResolveResult rootResult, String rootConfigurationName, DependencyToComponentIdResolver idResolver,
-                            ComponentMetaDataResolver metaDataResolver, DependencyToConfigurationResolver dependencyToConfigurationResolver,
-                            ArtifactResolver artifactResolver) {
+                            ComponentMetaDataResolver metaDataResolver, DependencyToConfigurationResolver dependencyToConfigurationResolver) {
             this.idResolver = idResolver;
             this.metaDataResolver = metaDataResolver;
             this.dependencyToConfigurationResolver = dependencyToConfigurationResolver;
-            this.artifactResolver = artifactResolver;
             ModuleVersionResolveState rootVersion = getRevision(rootResult.getId());
             rootVersion.setMetaData(rootResult.getMetaData());
             root = new RootConfigurationNode(rootVersion, new ResolvedConfigurationIdentifier(rootVersion.id, rootConfigurationName), this);
@@ -367,7 +366,7 @@ public class DependencyGraphBuilder {
             ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(module.id, configurationName);
             ConfigurationNode configuration = nodes.get(id);
             if (configuration == null) {
-                configuration = new ConfigurationNode(module, id, this);
+                configuration = new ConfigurationNode(id, module, this);
                 nodes.put(id, configuration);
             }
             return configuration;
@@ -573,7 +572,7 @@ public class DependencyGraphBuilder {
             }
 
             DefaultBuildableComponentResolveResult result = new DefaultBuildableComponentResolveResult();
-            resolver.resolve(firstReference.dependencyMetaData, idResolveResult.getId(), result);
+            resolver.resolve(idResolveResult.getId(), DefaultComponentOverrideMetadata.forDependency(firstReference.dependencyMetaData), result);
             if (result.getFailure() != null) {
                 failure = result.getFailure();
                 return;
@@ -625,21 +624,19 @@ public class DependencyGraphBuilder {
      */
     static class ConfigurationNode {
         public final ModuleVersionResolveState moduleRevision;
-        public final ConfigurationMetaData metaData;
         public final Set<DependencyEdge> incomingEdges = new LinkedHashSet<DependencyEdge>();
         public final Set<DependencyEdge> outgoingEdges = new LinkedHashSet<DependencyEdge>();
         public final ResolvedConfigurationIdentifier id;
 
+        private final ConfigurationMetaData metaData;
         private final ResolveState resolveState;
         private ModuleResolutionFilter previousTraversal;
-        private Set<ComponentArtifactMetaData> artifacts;
-        private Map<IvyArtifactName, ResolvedArtifact> resolvedArtifacts = new HashMap<IvyArtifactName, ResolvedArtifact>();
 
-        private ConfigurationNode(ModuleVersionResolveState moduleRevision, ResolvedConfigurationIdentifier id, ResolveState resolveState) {
+        private ConfigurationNode(ResolvedConfigurationIdentifier id, ModuleVersionResolveState moduleRevision, ResolveState resolveState) {
+            this.id = id;
             this.moduleRevision = moduleRevision;
             this.resolveState = resolveState;
             this.metaData = moduleRevision.metaData.getConfiguration(id.getConfiguration());
-            this.id = id;
             moduleRevision.addConfiguration(this);
         }
 
@@ -647,34 +644,18 @@ public class DependencyGraphBuilder {
             return moduleRevision.id;
         }
 
-        @Override
-        public String toString() {
-            return String.format("%s(%s)", moduleRevision, metaData.getName());
+        public ComponentIdentifier getComponentId() {
+            return moduleRevision.getComponentId();
         }
 
-        public Set<ResolvedArtifact> getArtifacts(ResolvedConfigurationBuilder builder, ModuleResolutionFilter moduleResolutionFilter) {
-            if (artifacts == null) {
-                BuildableArtifactSetResolveResult result = new DefaultBuildableArtifactSetResolveResult();
-                resolveState.artifactResolver.resolveModuleArtifacts(metaData.getComponent(), new DefaultComponentUsage(metaData.getName()), result);
-                artifacts = result.getArtifacts();
-            }
-
-            Set<ResolvedArtifact> result = new LinkedHashSet<ResolvedArtifact>();
-            ModuleIdentifier moduleId = id.getId().getModule();
-            for (ComponentArtifactMetaData artifact : artifacts) {
-                IvyArtifactName artifactName = artifact.getName();
-                if (!moduleResolutionFilter.acceptArtifact(moduleId, artifactName)) {
-                    continue;
-                }
-                ResolvedArtifact resolvedArtifact = resolvedArtifacts.get(artifactName);
-                if (resolvedArtifact == null) {
-                    resolvedArtifact = builder.newArtifact(id, metaData.getComponent(), artifact, resolveState.artifactResolver);
-                    resolvedArtifacts.put(artifactName, resolvedArtifact);
-                }
-                result.add(resolvedArtifact);
-            }
+        // TODO:DAZ Need to get rid of this
+        public ConfigurationMetaData getMetaData() {
+            return metaData;
+        }
 
-            return result;
+        @Override
+        public String toString() {
+            return String.format("%s(%s)", id.getId(), id.getConfiguration());
         }
 
         public boolean isTransitive() {
@@ -812,7 +793,7 @@ public class DependencyGraphBuilder {
 
     private static class RootConfigurationNode extends ConfigurationNode {
         private RootConfigurationNode(ModuleVersionResolveState moduleRevision, ResolvedConfigurationIdentifier id, ResolveState resolveState) {
-            super(moduleRevision, id, resolveState);
+            super(id, moduleRevision, resolveState);
         }
 
         @Override
@@ -908,7 +889,7 @@ public class DependencyGraphBuilder {
         public <T extends ComponentResolutionState> T select(Collection<? extends T> candidates) {
             for (ConfigurationNode configuration : root.configurations) {
                 for (DependencyEdge outgoingEdge : configuration.outgoingEdges) {
-                    if (outgoingEdge.dependencyDescriptor.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) {
+                    if (outgoingEdge.dependencyMetaData.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) {
                         outgoingEdge.targetModuleRevision.selectionReason = VersionSelectionReasons.FORCED;
                         return (T) outgoingEdge.targetModuleRevision;
                     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolutionResultDependencyGraphVisitor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolutionResultDependencyGraphVisitor.java
index f8b1e8c..02dc4c3 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolutionResultDependencyGraphVisitor.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolutionResultDependencyGraphVisitor.java
@@ -26,7 +26,7 @@ class ResolutionResultDependencyGraphVisitor implements DependencyGraphVisitor {
     }
 
     public void start(DependencyGraphBuilder.ConfigurationNode root) {
-        newModelBuilder.start(root.toId(), root.metaData.getComponent().getComponentId());
+        newModelBuilder.start(root.toId(), root.getComponentId());
     }
 
     public void visitNode(DependencyGraphBuilder.ConfigurationNode resolvedConfiguration) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedConfigurationDependencyGraphVisitor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedConfigurationDependencyGraphVisitor.java
index c5674ba..9be9578 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedConfigurationDependencyGraphVisitor.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedConfigurationDependencyGraphVisitor.java
@@ -16,16 +16,23 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph;
 
+import com.google.common.collect.Maps;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
-import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.api.internal.artifacts.ivyservice.DefaultUnresolvedDependency;
-import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedArtifactsBuilder;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.ResolvedConfigurationBuilder;
+import org.gradle.internal.component.model.ComponentArtifactIdentifier;
 import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.ComponentResolveMetaData;
+import org.gradle.internal.component.model.ConfigurationMetaData;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,13 +41,18 @@ import java.util.*;
 class ResolvedConfigurationDependencyGraphVisitor implements DependencyGraphVisitor {
     private static final Logger LOGGER = LoggerFactory.getLogger(ResolvedConfigurationDependencyGraphVisitor.class);
 
+    private final IdGenerator<Long> idGenerator = new LongIdGenerator();
     private final ResolvedConfigurationBuilder builder;
+    private final ResolvedArtifactsBuilder artifactsBuilder;
     private final ArtifactResolver artifactResolver;
     private final Map<ModuleVersionSelector, BrokenDependency> failuresByRevisionId = new LinkedHashMap<ModuleVersionSelector, BrokenDependency>();
+    private final Map<ComponentArtifactIdentifier, ResolvedArtifact> allResolvedArtifacts = Maps.newHashMap();
+    private final Map<ResolvedConfigurationIdentifier, ArtifactSet> artifactSetsByConfiguration = Maps.newHashMap();
     private DependencyGraphBuilder.ConfigurationNode root;
 
-    ResolvedConfigurationDependencyGraphVisitor(ResolvedConfigurationBuilder builder, ArtifactResolver artifactResolver) {
+    ResolvedConfigurationDependencyGraphVisitor(ResolvedConfigurationBuilder builder, ResolvedArtifactsBuilder artifactsBuilder, ArtifactResolver artifactResolver) {
         this.builder = builder;
+        this.artifactsBuilder = artifactsBuilder;
         this.artifactResolver = artifactResolver;
     }
 
@@ -61,35 +73,54 @@ class ResolvedConfigurationDependencyGraphVisitor implements DependencyGraphVisi
     public void visitEdge(DependencyGraphBuilder.ConfigurationNode resolvedConfiguration) {
         LOGGER.debug("Attaching {} to its parents.", resolvedConfiguration);
         for (DependencyGraphBuilder.DependencyEdge dependency : resolvedConfiguration.incomingEdges) {
-            attachToParents(dependency, resolvedConfiguration, builder);
+            attachToParents(dependency, resolvedConfiguration);
         }
     }
 
-    private void attachToParents(DependencyGraphBuilder.DependencyEdge dependency, DependencyGraphBuilder.ConfigurationNode childConfiguration, ResolvedConfigurationBuilder oldModelBuilder) {
+    private void attachToParents(DependencyGraphBuilder.DependencyEdge dependency, DependencyGraphBuilder.ConfigurationNode childConfiguration) {
         ResolvedConfigurationIdentifier parent = dependency.from.id;
         ResolvedConfigurationIdentifier child = childConfiguration.id;
-        oldModelBuilder.addChild(parent, child);
-        oldModelBuilder.addParentSpecificArtifacts(child, parent, getArtifacts(dependency, childConfiguration, oldModelBuilder));
+        builder.addChild(parent, child);
+
+        ArtifactSet artifacts = getArtifacts(dependency, childConfiguration);
+        builder.addArtifacts(child, parent, artifacts.getId());
+        artifactsBuilder.addArtifacts(artifacts.getId(), artifacts);
 
         if (parent == root.id) {
             ModuleDependency moduleDependency = dependency.getModuleDependency();
-            oldModelBuilder.addFirstLevelDependency(moduleDependency, child);
+            builder.addFirstLevelDependency(moduleDependency, child);
         }
     }
 
-    private Set<ResolvedArtifact> getArtifacts(DependencyGraphBuilder.DependencyEdge dependency, DependencyGraphBuilder.ConfigurationNode childConfiguration, ResolvedConfigurationBuilder builder) {
-        Set<ComponentArtifactMetaData> dependencyArtifacts = dependency.getArtifacts(childConfiguration.metaData);
-        if (dependencyArtifacts.isEmpty()) {
-            return childConfiguration.getArtifacts(builder, dependency.getSelector());
+    // TODO:DAZ This is functional, but need to refactor for clarity
+    private ArtifactSet getArtifacts(DependencyGraphBuilder.DependencyEdge dependency, DependencyGraphBuilder.ConfigurationNode childConfiguration) {
+        long id = idGenerator.generateId();
+        ResolvedConfigurationIdentifier configurationIdentifier = childConfiguration.id;
+        ConfigurationMetaData metaData = childConfiguration.getMetaData();
+        ComponentResolveMetaData component = metaData.getComponent();
+
+        Set<ComponentArtifactMetaData> artifacts = dependency.getArtifacts(metaData);
+        if (!artifacts.isEmpty()) {
+            return new DependencyArtifactSet(component.getId(), component.getSource(), artifacts, artifactResolver, allResolvedArtifacts, id);
         }
-        Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
-        for (ComponentArtifactMetaData artifact : dependencyArtifacts) {
-            artifacts.add(builder.newArtifact(childConfiguration.id, childConfiguration.metaData.getComponent(), artifact, artifactResolver));
+
+        ArtifactSet configurationArtifactSet = artifactSetsByConfiguration.get(configurationIdentifier);
+        if (configurationArtifactSet == null) {
+
+            configurationArtifactSet = new ConfigurationArtifactSet(component, configurationIdentifier, dependency.getSelector(), artifactResolver, allResolvedArtifacts, id);
+
+            // Only share an ArtifactSet if the artifacts are not filtered by the dependency
+            if (dependency.getSelector().acceptsAllArtifacts()) {
+                artifactSetsByConfiguration.put(configurationIdentifier, configurationArtifactSet);
+            }
         }
-        return artifacts;
+
+        return configurationArtifactSet;
     }
 
     public void finish(DependencyGraphBuilder.ConfigurationNode root) {
+        allResolvedArtifacts.clear();
+        artifactSetsByConfiguration.clear();
         attachFailures(builder);
         builder.done(root.id);
     }
@@ -175,4 +206,5 @@ class ResolvedConfigurationDependencyGraphVisitor implements DependencyGraphVisi
             this.failure = failure;
         }
     }
+
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedProjectConfigurationResultGraphVisitor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedProjectConfigurationResultGraphVisitor.java
index b6f459e..b49fa9c 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedProjectConfigurationResultGraphVisitor.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/graph/ResolvedProjectConfigurationResultGraphVisitor.java
@@ -29,12 +29,12 @@ public class ResolvedProjectConfigurationResultGraphVisitor implements Dependenc
 
     @Override
     public void start(DependencyGraphBuilder.ConfigurationNode root) {
-        builder.registerRoot(root.metaData.getComponent().getComponentId());
+        builder.registerRoot(root.getComponentId());
     }
 
     @Override
     public void visitNode(DependencyGraphBuilder.ConfigurationNode resolvedConfiguration) {
-        ComponentIdentifier componentId = resolvedConfiguration.metaData.getComponent().getComponentId();
+        ComponentIdentifier componentId = resolvedConfiguration.getComponentId();
         if (componentId instanceof ProjectComponentIdentifier) {
             builder.addProjectComponentResult((ProjectComponentIdentifier) componentId, resolvedConfiguration.id.getConfiguration());
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactResults.java
new file mode 100644
index 0000000..c4f9f02
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactResults.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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 com.google.common.collect.Maps;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.ArtifactSet;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DefaultResolvedArtifactResults implements ResolvedArtifactResults {
+    private Map<Long, ArtifactSet> artifactSets = Maps.newHashMap();
+    private Set<ResolvedArtifact> artifacts;
+    private Map<Long, Set<ResolvedArtifact>> resolvedArtifactsById;
+
+    @Override
+    public Set<ResolvedArtifact> getArtifacts() {
+        assertArtifactsResolved();
+        return new LinkedHashSet<ResolvedArtifact>(artifacts);
+    }
+
+    @Override
+    public Set<ResolvedArtifact> getArtifacts(long id) {
+        assertArtifactsResolved();
+        Set<ResolvedArtifact> a = resolvedArtifactsById.get(id);
+        assert a != null : "Unable to find artifacts for id: " + id;
+        return a;
+    }
+
+    public void addArtifactSet(long id, ArtifactSet artifactSet) {
+        artifactSets.put(id, artifactSet);
+    }
+
+    public void resolveNow() {
+        if (artifacts == null) {
+            artifacts = new LinkedHashSet<ResolvedArtifact>();
+            resolvedArtifactsById = new LinkedHashMap<Long, Set<ResolvedArtifact>>();
+            for (Map.Entry<Long, ArtifactSet> entry : artifactSets.entrySet()) {
+                Set<ResolvedArtifact> resolvedArtifacts = entry.getValue().getArtifacts();
+                artifacts.addAll(resolvedArtifacts);
+                resolvedArtifactsById.put(entry.getKey(), resolvedArtifacts);
+            }
+
+            // Release ResolvedArtifactSet instances so we're not holding onto state
+            artifactSets = null;
+        }
+    }
+
+    private void assertArtifactsResolved() {
+        if (artifacts == null) {
+            throw new IllegalStateException("Cannot access artifacts before they are explicitly resolved.");
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactsBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactsBuilder.java
new file mode 100644
index 0000000..3bfa26b
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedArtifactsBuilder.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.ivyservice.resolveengine.graph.ArtifactSet;
+
+public class DefaultResolvedArtifactsBuilder implements ResolvedArtifactsBuilder {
+    private final DefaultResolvedArtifactResults artifactResults = new DefaultResolvedArtifactResults();
+
+    public void addArtifacts(long id, ArtifactSet artifactSet) {
+        artifactResults.addArtifactSet(id, artifactSet);
+    }
+
+    @Override
+    public ResolvedArtifactResults resolve() {
+        artifactResults.resolveNow();
+        return artifactResults;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java
index cf46273..aca0c39 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedConfigurationBuilder.java
@@ -17,29 +17,17 @@
 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.DefaultResolvedArtifact;
 import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
-import org.gradle.internal.component.model.ComponentResolveMetaData;
-import org.gradle.internal.resolve.resolver.ArtifactResolver;
-import org.gradle.internal.resolve.result.DefaultBuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
-import org.gradle.internal.component.model.ModuleSource;
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-import org.gradle.internal.Factory;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.internal.id.LongIdGenerator;
 
-import java.io.File;
-import java.util.*;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
 
-public class DefaultResolvedConfigurationBuilder implements
-        ResolvedConfigurationBuilder, ResolvedConfigurationResults, ResolvedContentsMapping {
+public class DefaultResolvedConfigurationBuilder implements ResolvedConfigurationBuilder {
 
-    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;
@@ -67,67 +55,16 @@ public class DefaultResolvedConfigurationBuilder implements
         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 addArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, long artifactSet) {
+        builder.parentSpecificArtifacts(child, parent, artifactSet);
     }
 
     public void newResolvedDependency(ResolvedConfigurationIdentifier id) {
         builder.resolvedDependency(id);
     }
 
-    public ResolvedArtifact newArtifact(ResolvedConfigurationIdentifier owner, ComponentResolveMetaData component, ComponentArtifactMetaData artifact, ArtifactResolver artifactResolver) {
-        Factory<File> artifactSource = new LazyArtifactSource(artifact, component.getSource(), artifactResolver);
-        long id = idGenerator.generateId();
-        ResolvedArtifact newArtifact = new DefaultResolvedArtifact(new DefaultResolvedModuleVersion(owner.getId()), 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();
-        }
+    @Override
+    public ResolvedGraphResults complete() {
+        return new DefaultResolvedGraphResults(unresolvedDependencies, modulesMap);
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedGraphResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedGraphResults.java
new file mode 100644
index 0000000..7a7cd4b
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/DefaultResolvedGraphResults.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.UnresolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+
+import java.util.Map;
+import java.util.Set;
+
+public class DefaultResolvedGraphResults implements ResolvedGraphResults {
+    private final Set<UnresolvedDependency> unresolvedDependencies;
+    private final Map<ResolvedConfigurationIdentifier, ModuleDependency> modulesMap;
+
+    public DefaultResolvedGraphResults(Set<UnresolvedDependency> unresolvedDependencies, Map<ResolvedConfigurationIdentifier, ModuleDependency> modulesMap) {
+        this.unresolvedDependencies = unresolvedDependencies;
+        this.modulesMap = modulesMap;
+    }
+
+    @Override
+    public boolean hasError() {
+        return !unresolvedDependencies.isEmpty();
+    }
+
+    @Override
+    public Set<UnresolvedDependency> getUnresolvedDependencies() {
+        return unresolvedDependencies;
+    }
+
+    @Override
+    public ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id) {
+        ModuleDependency m = modulesMap.get(id);
+        assert m != null : "Unable to find module dependency for id: " + id;
+        return m;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactResults.java
new file mode 100644
index 0000000..f392d57
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactResults.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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 java.util.Set;
+
+public interface ResolvedArtifactResults {
+    void resolveNow();
+
+    Set<ResolvedArtifact> getArtifacts();
+
+    Set<ResolvedArtifact> getArtifacts(long id);
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactsBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactsBuilder.java
new file mode 100644
index 0000000..bd2a8bd
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedArtifactsBuilder.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.ivyservice.resolveengine.graph.ArtifactSet;
+
+public interface ResolvedArtifactsBuilder {
+
+    // TODO:DAZ Should have a component id here, instead of relying on an internal id for syncing with graph
+    void addArtifacts(long id, ArtifactSet artifacts);
+
+    ResolvedArtifactResults resolve();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java
index 5fffd8e..19007cb 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationBuilder.java
@@ -16,14 +16,8 @@
 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.internal.component.model.ComponentResolveMetaData;
-import org.gradle.internal.resolve.resolver.ArtifactResolver;
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-
-import java.util.Set;
 
 //builds old model of resolved dependency graph based on the result events
 public interface ResolvedConfigurationBuilder {
@@ -34,11 +28,11 @@ public interface ResolvedConfigurationBuilder {
 
     void addChild(ResolvedConfigurationIdentifier parent, ResolvedConfigurationIdentifier child);
 
-    void done(ResolvedConfigurationIdentifier root);
-
-    void addParentSpecificArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, Set<ResolvedArtifact> artifacts);
+    void addArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, long artifactsId);
 
     void newResolvedDependency(ResolvedConfigurationIdentifier id);
 
-    ResolvedArtifact newArtifact(ResolvedConfigurationIdentifier owner, ComponentResolveMetaData component, ComponentArtifactMetaData artifact, ArtifactResolver artifactResolver);
+    void done(ResolvedConfigurationIdentifier root);
+
+    ResolvedGraphResults complete();
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.java
deleted file mode 100644
index 34c0cb0..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedConfigurationResults.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.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/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java
index 7fb9266..8cb7f44 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedContentsMapping.java
@@ -20,9 +20,11 @@ import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
 
+import java.util.Set;
+
 public interface ResolvedContentsMapping {
 
-    ResolvedArtifact getArtifact(long id);
+    Set<ResolvedArtifact> getArtifacts(long id);
 
     ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedGraphResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedGraphResults.java
new file mode 100644
index 0000000..bac3fdf
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/ResolvedGraphResults.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.UnresolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+
+import java.util.Set;
+
+public interface ResolvedGraphResults {
+    boolean hasError();
+
+    Set<UnresolvedDependency> getUnresolvedDependencies();
+
+    ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id);
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java
index 8b1206f..bc50b38 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsBuilder.java
@@ -88,7 +88,7 @@ public class TransientConfigurationResultsBuilder {
         writeId(PARENT_CHILD, parent, child);
     }
 
-    public void parentSpecificArtifact(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, final long artifactId) {
+    public void parentSpecificArtifacts(ResolvedConfigurationIdentifier child, ResolvedConfigurationIdentifier parent, final long artifactId) {
         writeId(PARENT_ARTIFACT, child, parent);
         binaryStore.write(new BinaryStore.WriteAction() {
             public void write(Encoder encoder) throws IOException {
@@ -176,7 +176,7 @@ public class TransientConfigurationResultsBuilder {
                         if (artifactChild == null) {
                             throw new IllegalStateException(String.format("Unexpected child dependency id %s. Seen ids: %s", artifactChildId, allDependencies.keySet()));
                         }
-                        artifactParent.addParentSpecificArtifacts(artifactChild, newHashSet(mapping.getArtifact(decoder.readLong())));
+                        artifactParent.addParentSpecificArtifacts(artifactChild, newHashSet(mapping.getArtifacts(decoder.readLong())));
                         break;
                     default:
                         throw new IOException("Unknown value type read from stream: " + type);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsLoader.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsLoader.java
new file mode 100644
index 0000000..fe2b96a
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/oldresult/TransientConfigurationResultsLoader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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;
+import org.gradle.internal.Factory;
+
+import java.util.Set;
+
+public class TransientConfigurationResultsLoader implements Factory<TransientConfigurationResults> {
+    private final TransientConfigurationResultsBuilder transientConfigurationResultsBuilder;
+    private final ResolvedArtifactResults artifactResults;
+    private final ResolvedGraphResults graphResults;
+
+    public TransientConfigurationResultsLoader(TransientConfigurationResultsBuilder transientConfigurationResultsBuilder, ResolvedGraphResults graphResults, ResolvedArtifactResults artifactResults) {
+        this.transientConfigurationResultsBuilder = transientConfigurationResultsBuilder;
+        this.artifactResults = artifactResults;
+        this.graphResults = graphResults;
+    }
+
+    @Override
+    public TransientConfigurationResults create() {
+        return transientConfigurationResultsBuilder.load(new ContentMapping());
+    }
+
+    private class ContentMapping implements ResolvedContentsMapping {
+        @Override
+        public Set<ResolvedArtifact> getArtifacts(long id) {
+            return artifactResults.getArtifacts(id);
+        }
+
+        @Override
+        public ModuleDependency getModuleDependency(ResolvedConfigurationIdentifier id) {
+            return graphResults.getModuleDependency(id);
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfiguration.java
new file mode 100644
index 0000000..64c5278
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfiguration.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.projectresult;
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+
+public class DefaultResolvedProjectConfiguration implements ResolvedProjectConfiguration {
+    private final ProjectComponentIdentifier id;
+    private final String targetConfiguration;
+
+    public DefaultResolvedProjectConfiguration(ProjectComponentIdentifier id, String targetConfiguration) {
+        this.id = id;
+        this.targetConfiguration = targetConfiguration;
+    }
+
+    @Override
+    public ProjectComponentIdentifier getId() {
+        return id;
+    }
+
+    @Override
+    public String getTargetConfiguration() {
+        return targetConfiguration;
+    }
+
+    @Override
+    public String toString() {
+        return id + ":" + targetConfiguration;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResult.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResult.java
deleted file mode 100644
index fe87fac..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResult.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.projectresult;
-
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class DefaultResolvedProjectConfigurationResult implements ResolvedProjectConfigurationResult {
-    private final ProjectComponentIdentifier id;
-    private final Set<String> targetConfigurations = new LinkedHashSet<String>();
-
-    public DefaultResolvedProjectConfigurationResult(ProjectComponentIdentifier id) {
-        this.id = id;
-    }
-
-    @Override
-    public ProjectComponentIdentifier getId() {
-        return id;
-    }
-
-    @Override
-    public Set<String> getTargetConfigurations() {
-        return targetConfigurations;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DefaultResolvedProjectConfigurationResult that = (DefaultResolvedProjectConfigurationResult) o;
-
-        if (!id.equals(that.id)) {
-            return false;
-        }
-        if (!targetConfigurations.equals(that.targetConfigurations)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = id.hashCode();
-        result = 31 * result + targetConfigurations.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return id + ":" + targetConfigurations;
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResultBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResultBuilder.java
index aa8349d..6a95eda 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResultBuilder.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResultBuilder.java
@@ -19,14 +19,18 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult
 import org.gradle.api.artifacts.component.ComponentIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 public class DefaultResolvedProjectConfigurationResultBuilder implements ResolvedProjectConfigurationResultBuilder {
-    private final Map<ProjectComponentIdentifier, DefaultResolvedProjectConfigurationResult> results = new LinkedHashMap<ProjectComponentIdentifier, DefaultResolvedProjectConfigurationResult>();
+    private final List<ResolvedProjectConfiguration> results = new ArrayList<ResolvedProjectConfiguration>();
+    private final boolean buildProjectDependencies;
     private ComponentIdentifier rootId;
 
+    public DefaultResolvedProjectConfigurationResultBuilder(boolean buildProjectDependencies) {
+        this.buildProjectDependencies = buildProjectDependencies;
+    }
+
     @Override
     public void registerRoot(ComponentIdentifier componentId) {
         this.rootId = componentId;
@@ -34,23 +38,17 @@ public class DefaultResolvedProjectConfigurationResultBuilder implements Resolve
 
     @Override
     public void addProjectComponentResult(ProjectComponentIdentifier componentId, String configurationName) {
-        if (rootId.equals(componentId)) {
+        if (!buildProjectDependencies) {
             return;
         }
-        getOrCreate(componentId).getTargetConfigurations().add(configurationName);
-    }
-
-    private DefaultResolvedProjectConfigurationResult getOrCreate(ProjectComponentIdentifier componentId) {
-        DefaultResolvedProjectConfigurationResult result = results.get(componentId);
-        if (result == null) {
-            result = new DefaultResolvedProjectConfigurationResult(componentId);
-            results.put(componentId, result);
+        if (rootId.equals(componentId)) {
+            return;
         }
-        return result;
+        results.add(new DefaultResolvedProjectConfiguration(componentId, configurationName));
     }
 
     @Override
     public ResolvedProjectConfigurationResults complete() {
-        return new DefaultResolvedProjectConfigurationResults(new LinkedHashSet<ResolvedProjectConfigurationResult>(results.values()));
+        return new DefaultResolvedProjectConfigurationResults(results);
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResults.java
index 9895117..0c699c9 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResults.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/DefaultResolvedProjectConfigurationResults.java
@@ -16,17 +16,17 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult;
 
-import java.util.Set;
+import java.util.Collection;
 
 public class DefaultResolvedProjectConfigurationResults implements ResolvedProjectConfigurationResults {
-    private final Set<ResolvedProjectConfigurationResult> results;
+    private final Collection<ResolvedProjectConfiguration> results;
 
-    public DefaultResolvedProjectConfigurationResults(Set<ResolvedProjectConfigurationResult> results) {
+    public DefaultResolvedProjectConfigurationResults(Collection<ResolvedProjectConfiguration> results) {
         this.results = results;
     }
 
     @Override
-    public Set<ResolvedProjectConfigurationResult> getAllProjectConfigurationResults() {
+    public Iterable<ResolvedProjectConfiguration> get() {
         return results;
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfiguration.java
new file mode 100644
index 0000000..0e5e304
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfiguration.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.projectresult;
+
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
+
+public interface ResolvedProjectConfiguration {
+    ProjectComponentIdentifier getId();
+
+    String getTargetConfiguration();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResult.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResult.java
deleted file mode 100644
index cc1d8f3..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResult.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.projectresult;
-
-import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-
-import java.util.Set;
-
-public interface ResolvedProjectConfigurationResult {
-    ProjectComponentIdentifier getId();
-
-    Set<String> getTargetConfigurations();
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResults.java
index 0a25524..b898c93 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResults.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/projectresult/ResolvedProjectConfigurationResults.java
@@ -16,8 +16,6 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult;
 
-import java.util.Set;
-
 public interface ResolvedProjectConfigurationResults {
-    Set<ResolvedProjectConfigurationResult> getAllProjectConfigurationResults();
+    Iterable<ResolvedProjectConfiguration> get();
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java
index 9958ab2..f0957a0 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializer.java
@@ -16,11 +16,13 @@
 
 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.LibraryComponentIdentifier;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
-import org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier;
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
+import org.gradle.internal.component.local.model.DefaultLibraryComponentIdentifier;
+import org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier;
 import org.gradle.internal.serialize.Decoder;
 import org.gradle.internal.serialize.Encoder;
 import org.gradle.internal.serialize.Serializer;
@@ -35,6 +37,8 @@ public class ComponentIdentifierSerializer implements Serializer<ComponentIdenti
             return new DefaultProjectComponentIdentifier(decoder.readString());
         } else if(Implementation.MODULE.getId() == id) {
             return new DefaultModuleComponentIdentifier(decoder.readString(), decoder.readString(), decoder.readString());
+        } else if (Implementation.LIBRARY.getId() == id) {
+            return new DefaultLibraryComponentIdentifier(decoder.readString(), decoder.readString());
         }
 
         throw new IllegalArgumentException("Unable to find component identifier with id: " + id);
@@ -55,13 +59,18 @@ public class ComponentIdentifierSerializer implements Serializer<ComponentIdenti
             ProjectComponentIdentifier projectComponentIdentifier = (ProjectComponentIdentifier)value;
             encoder.writeByte(Implementation.BUILD.getId());
             encoder.writeString(projectComponentIdentifier.getProjectPath());
+        } else if(value instanceof DefaultLibraryComponentIdentifier) {
+            LibraryComponentIdentifier libraryIdentifier = (LibraryComponentIdentifier)value;
+            encoder.writeByte(Implementation.LIBRARY.getId());
+            encoder.writeString(libraryIdentifier.getProjectPath());
+            encoder.writeString(libraryIdentifier.getLibraryName());
         } else {
             throw new IllegalArgumentException("Unsupported component identifier class: " + value.getClass());
         }
     }
 
     private static enum Implementation {
-        MODULE((byte) 1), BUILD((byte) 2);
+        MODULE((byte) 1), BUILD((byte) 2), LIBRARY((byte) 3);
 
         private final byte id;
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java
index 9265777..414208e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializer.java
@@ -16,11 +16,13 @@
 
 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.LibraryComponentSelector;
 import org.gradle.api.artifacts.component.ModuleComponentSelector;
-import org.gradle.internal.component.local.model.DefaultProjectComponentSelector;
+import org.gradle.api.artifacts.component.ProjectComponentSelector;
 import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
+import org.gradle.internal.component.local.model.DefaultLibraryComponentSelector;
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector;
 import org.gradle.internal.serialize.Decoder;
 import org.gradle.internal.serialize.Encoder;
 import org.gradle.internal.serialize.Serializer;
@@ -31,37 +33,44 @@ public class ComponentSelectorSerializer implements Serializer<ComponentSelector
     public ComponentSelector read(Decoder decoder) throws IOException {
         byte id = decoder.readByte();
 
-        if(Implementation.BUILD.getId() == id) {
+        if (Implementation.BUILD.getId() == id) {
             return new DefaultProjectComponentSelector(decoder.readString());
-        } else if(Implementation.MODULE.getId() == id) {
+        } else if (Implementation.MODULE.getId() == id) {
             return new DefaultModuleComponentSelector(decoder.readString(), decoder.readString(), decoder.readString());
+        } else if (Implementation.LIBRARY.getId() == id) {
+            return new DefaultLibraryComponentSelector(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) {
+        if (value == null) {
             throw new IllegalArgumentException("Provided component selector may not be null");
         }
 
-        if(value instanceof DefaultModuleComponentSelector) {
-            ModuleComponentSelector moduleComponentSelector = (ModuleComponentSelector)value;
+        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;
+        } else if (value instanceof DefaultProjectComponentSelector) {
+            ProjectComponentSelector projectComponentSelector = (ProjectComponentSelector) value;
             encoder.writeByte(Implementation.BUILD.getId());
             encoder.writeString(projectComponentSelector.getProjectPath());
+        } else if (value instanceof DefaultLibraryComponentSelector) {
+            LibraryComponentSelector libraryComponentSelector = (LibraryComponentSelector) value;
+            encoder.writeByte(Implementation.LIBRARY.getId());
+            encoder.writeString(libraryComponentSelector.getProjectPath());
+            encoder.writeString(libraryComponentSelector.getLibraryName());
         } else {
             throw new IllegalArgumentException("Unsupported component selector class: " + value.getClass());
         }
     }
 
     private static enum Implementation {
-        MODULE((byte) 1), BUILD((byte) 2);
+        MODULE((byte) 1), BUILD((byte) 2), LIBRARY((byte) 3);
 
         private final byte id;
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java
index 7b731fd..245f2e0 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStore.java
@@ -82,7 +82,9 @@ class DefaultBinaryStore implements BinaryStore, Closeable {
                 encoder.close();
             }
         } finally {
-            file.delete();
+            if (file != null) {
+                file.delete();
+            }
             encoder = null;
             file = null;
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
index 5952502..1ff205e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.artifacts.mvnsettings;
 
-import org.gradle.mvn3.org.apache.maven.settings.Settings;
-import org.gradle.mvn3.org.apache.maven.settings.building.SettingsBuildingException;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.settings.building.SettingsBuildingException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java
index cc39a17..01eb522 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java
@@ -15,8 +15,8 @@
  */
 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.apache.maven.settings.Settings;
+import org.apache.maven.settings.building.*;
 
 public class DefaultMavenSettingsProvider implements MavenSettingsProvider {
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java
index dd93102..a7d7030 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.artifacts.mvnsettings;
 
-import org.gradle.mvn3.org.apache.maven.settings.Settings;
-import org.gradle.mvn3.org.apache.maven.settings.building.SettingsBuildingException;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.settings.building.SettingsBuildingException;
 
 public interface MavenSettingsProvider {
     Settings buildSettings() throws SettingsBuildingException;
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQuery.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQuery.java
index f3f95ce..4574d04 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQuery.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQuery.java
@@ -19,8 +19,8 @@ import com.google.common.collect.Sets;
 import org.gradle.api.artifacts.component.ComponentIdentifier;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
-import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
 import org.gradle.api.artifacts.result.ArtifactResolutionResult;
 import org.gradle.api.artifacts.result.ComponentArtifactsResult;
 import org.gradle.api.artifacts.result.ComponentResult;
@@ -28,20 +28,20 @@ import org.gradle.api.component.Artifact;
 import org.gradle.api.component.Component;
 import org.gradle.api.internal.artifacts.GlobalDependencyResolutionRules;
 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.configurations.ResolutionStrategyInternal;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 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.internal.component.model.ComponentArtifactMetaData;
-import org.gradle.internal.component.model.ComponentResolveMetaData;
-import org.gradle.internal.component.model.DefaultDependencyMetaData;
 import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.internal.artifacts.result.*;
 import org.gradle.api.internal.component.ArtifactType;
 import org.gradle.api.internal.component.ComponentTypeRegistry;
 import org.gradle.internal.Factory;
 import org.gradle.internal.Transformers;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.ComponentResolveMetaData;
+import org.gradle.internal.component.model.DefaultComponentOverrideMetadata;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
 import org.gradle.internal.resolve.result.*;
 import org.gradle.util.CollectionUtils;
@@ -98,8 +98,8 @@ public class DefaultArtifactResolutionQuery implements ArtifactResolutionQuery {
             throw new IllegalStateException("Must specify component type and artifacts to query.");
         }
         List<ResolutionAwareRepository> repositories = CollectionUtils.collect(repositoryHandler, Transformers.cast(ResolutionAwareRepository.class));
-        ConfigurationInternal configuration = configurationContainer.detachedConfiguration();
-        final RepositoryChain repositoryChain = ivyFactory.create(configuration, repositories, metadataHandler.getComponentMetadataProcessor());
+        ResolutionStrategyInternal resolutionStrategy = configurationContainer.detachedConfiguration().getResolutionStrategy();
+        final RepositoryChain repositoryChain = ivyFactory.create(resolutionStrategy, repositories, metadataHandler.getComponentMetadataProcessor());
         final ArtifactResolver artifactResolver = new ErrorHandlingArtifactResolver(repositoryChain.getArtifactResolver());
 
         return lockingManager.useCache("resolve artifacts", new Factory<ArtifactResolutionResult>() {
@@ -108,8 +108,8 @@ public class DefaultArtifactResolutionQuery implements ArtifactResolutionQuery {
 
                 for (ComponentIdentifier componentId : componentIds) {
                     try {
-                        ModuleComponentIdentifier moduleComponentId = validateComponentIdentifier(componentId);
-                        componentResults.add(buildComponentResult(moduleComponentId, repositoryChain, artifactResolver));
+                        ComponentIdentifier validId = validateComponentIdentifier(componentId);
+                        componentResults.add(buildComponentResult(validId, repositoryChain, artifactResolver));
                     } catch (Throwable t) {
                         componentResults.add(new DefaultUnresolvedComponentResult(componentId, t));
                     }
@@ -118,9 +118,9 @@ public class DefaultArtifactResolutionQuery implements ArtifactResolutionQuery {
                 return new DefaultArtifactResolutionResult(componentResults);
             }
 
-            private ModuleComponentIdentifier validateComponentIdentifier(ComponentIdentifier componentId) {
+            private ComponentIdentifier validateComponentIdentifier(ComponentIdentifier componentId) {
                 if (componentId instanceof ModuleComponentIdentifier) {
-                    return (ModuleComponentIdentifier) componentId;
+                    return componentId;
                 }
                 if(componentId instanceof ProjectComponentIdentifier) {
                     throw new IllegalArgumentException(String.format("Cannot query artifacts for a project component (%s).", componentId.getDisplayName()));
@@ -131,9 +131,9 @@ public class DefaultArtifactResolutionQuery implements ArtifactResolutionQuery {
         });
     }
 
-    private ComponentArtifactsResult buildComponentResult(ModuleComponentIdentifier moduleComponentId, RepositoryChain repositoryChain, ArtifactResolver artifactResolver) {
+    private ComponentArtifactsResult buildComponentResult(ComponentIdentifier componentId, RepositoryChain repositoryChain, ArtifactResolver artifactResolver) {
         BuildableComponentResolveResult moduleResolveResult = new DefaultBuildableComponentResolveResult();
-        repositoryChain.getDependencyResolver().resolve(new DefaultDependencyMetaData(moduleComponentId), moduleResolveResult);
+        repositoryChain.getComponentResolver().resolve(componentId, new DefaultComponentOverrideMetadata(), moduleResolveResult);
         ComponentResolveMetaData component = moduleResolveResult.getMetaData();
         DefaultComponentArtifactsResult componentResult = new DefaultComponentArtifactsResult(component.getComponentId());
         for (Class<? extends Artifact> artifactType : artifactTypes) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
index 97f521e..a7bc9c0 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
@@ -127,7 +127,7 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
         listVersionsForAllPatterns(ivyPatterns, metaDataArtifact, visitor);
 
         // List modules with missing metadata files
-        for (IvyArtifactName otherArtifact : getDependencyArtifactNames(dependency)) {
+        for (IvyArtifactName otherArtifact : getDependencyArtifactNames(dependency.getRequested().getName(), dependency.getArtifacts())) {
             listVersionsForAllPatterns(artifactPatterns, otherArtifact, visitor);
         }
         result.listed(versions);
@@ -139,11 +139,11 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
         }
     }
 
-    protected void doResolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-        resolveStaticDependency(dependency, moduleComponentIdentifier, result, createArtifactResolver());
+    protected void doResolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata prescribedMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+        resolveStaticDependency(moduleComponentIdentifier, prescribedMetaData, result, createArtifactResolver());
     }
 
-    protected final void resolveStaticDependency(DependencyMetaData dependency, ModuleComponentIdentifier moduleVersionIdentifier, BuildableModuleComponentMetaDataResolveResult result, ExternalResourceArtifactResolver artifactResolver) {
+    protected final void resolveStaticDependency(ModuleComponentIdentifier moduleVersionIdentifier, ComponentOverrideMetadata prescribedMetaData, BuildableModuleComponentMetaDataResolveResult result, ExternalResourceArtifactResolver artifactResolver) {
         MutableModuleComponentResolveMetaData metaDataArtifactMetaData = parseMetaDataFromArtifact(moduleVersionIdentifier, artifactResolver, result);
         if (metaDataArtifactMetaData != null) {
             LOGGER.debug("Metadata file found for module '{}' in repository '{}'.", moduleVersionIdentifier, getName());
@@ -151,7 +151,7 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
             return;
         }
 
-        MutableModuleComponentResolveMetaData metaDataFromDefaultArtifact = createMetaDataFromDefaultArtifact(moduleVersionIdentifier, dependency, artifactResolver, result);
+        MutableModuleComponentResolveMetaData metaDataFromDefaultArtifact = createMetaDataFromDefaultArtifact(moduleVersionIdentifier, prescribedMetaData, artifactResolver, result);
         if (metaDataFromDefaultArtifact != null) {
             LOGGER.debug("Found artifact but no meta-data for module '{}' in repository '{}', using default meta-data.", moduleVersionIdentifier, getName());
             result.resolved(metaDataFromDefaultArtifact);
@@ -174,23 +174,23 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
         return parseMetaDataFromResource(moduleComponentIdentifier, metaDataResource, context);
     }
 
-    private MutableModuleComponentResolveMetaData createMetaDataFromDefaultArtifact(ModuleComponentIdentifier moduleVersionIdentifier, DependencyMetaData dependency, ExternalResourceArtifactResolver artifactResolver, ResourceAwareResolveResult result) {
-        for (IvyArtifactName artifact : getDependencyArtifactNames(dependency)) {
-            if (artifactResolver.artifactExists(new DefaultModuleComponentArtifactMetaData(moduleVersionIdentifier, artifact), result)) {
-                return createMetaDataForDependency(dependency);
+    private MutableModuleComponentResolveMetaData createMetaDataFromDefaultArtifact(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata overrideMetadata, ExternalResourceArtifactResolver artifactResolver, ResourceAwareResolveResult result) {
+        Set<IvyArtifactName> artifacts = overrideMetadata.getArtifacts();
+        for (IvyArtifactName artifact : getDependencyArtifactNames(moduleComponentIdentifier.getModule(), artifacts)) {
+            if (artifactResolver.artifactExists(new DefaultModuleComponentArtifactMetaData(moduleComponentIdentifier, artifact), result)) {
+                return createDefaultComponentResolveMetaData(moduleComponentIdentifier, artifacts);
             }
         }
         return null;
     }
 
-    protected abstract MutableModuleComponentResolveMetaData createMetaDataForDependency(DependencyMetaData dependency);
+    protected abstract MutableModuleComponentResolveMetaData createDefaultComponentResolveMetaData(ModuleComponentIdentifier moduleComponentIdentifier, Set<IvyArtifactName> artifacts);
 
     protected abstract MutableModuleComponentResolveMetaData parseMetaDataFromResource(ModuleComponentIdentifier moduleComponentIdentifier, LocallyAvailableExternalResource cachedResource, DescriptorParseContext context);
 
-    private Set<IvyArtifactName> getDependencyArtifactNames(DependencyMetaData dependency) {
-        String moduleName = dependency.getRequested().getName();
+    private Set<IvyArtifactName> getDependencyArtifactNames(String moduleName, Set<IvyArtifactName> artifacts) {
         Set<IvyArtifactName> artifactSet = Sets.newLinkedHashSet();
-        artifactSet.addAll(dependency.getArtifacts());
+        artifactSet.addAll(artifacts);
 
         if (artifactSet.isEmpty()) {
             artifactSet.add(new DefaultIvyArtifactName(moduleName, "jar", "jar", Collections.<String, String>emptyMap()));
@@ -363,12 +363,10 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
         }
 
         public void resolveModuleArtifacts(ComponentResolveMetaData component, ComponentUsage componentUsage, BuildableArtifactSetResolveResult result) {
-            String configurationName = componentUsage.getConfigurationName();
-             ConfigurationMetaData configuration = component.getConfiguration(configurationName);
-             resolveConfigurationArtifacts((ModuleComponentResolveMetaData) component, configuration, result);
+             resolveConfigurationArtifacts((ModuleComponentResolveMetaData) component, componentUsage, result);
         }
 
-        protected abstract void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result);
+        protected abstract void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ComponentUsage usage, BuildableArtifactSetResolveResult result);
 
         protected abstract void resolveMetaDataArtifacts(ModuleComponentResolveMetaData module, BuildableArtifactSetResolveResult result);
 
@@ -387,7 +385,7 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
         public final void listModuleVersions(DependencyMetaData dependency, BuildableModuleVersionListingResolveResult result) {
         }
 
-        public final void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+        public final void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
         }
 
         protected final void resolveMetaDataArtifacts(ModuleComponentResolveMetaData module, BuildableArtifactSetResolveResult result) {
@@ -410,8 +408,8 @@ public abstract class ExternalResourceResolver implements ModuleVersionPublisher
             doListModuleVersions(dependency, result);
         }
 
-        public final void resolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
-            doResolveComponentMetaData(dependency, moduleComponentIdentifier, result);
+        public final void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+            doResolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
         }
 
         @Override
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java
index c3f36f5..ac4bc95 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverDescriptorParseContext.java
@@ -15,18 +15,18 @@
  */
 package org.gradle.api.internal.artifacts.repositories.resolver;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChain;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-import org.gradle.internal.component.model.DefaultDependencyMetaData;
 import org.gradle.api.internal.component.ArtifactType;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.DefaultComponentOverrideMetadata;
 import org.gradle.internal.resolve.resolver.ArtifactResolver;
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
 import org.gradle.internal.resolve.result.*;
 import org.gradle.internal.resource.local.DefaultLocallyAvailableExternalResource;
-import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.gradle.internal.resource.local.DefaultLocallyAvailableResource;
+import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.gradle.internal.resource.local.LocallyAvailableResource;
 
 import java.io.File;
@@ -43,16 +43,16 @@ public class ExternalResourceResolverDescriptorParseContext implements Descripto
         this.mainResolvers = mainResolvers;
     }
 
-    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleVersionIdentifier moduleVersionIdentifier, ArtifactType artifactType) {
-        File resolvedArtifactFile = resolveMetaDataArtifactFile(moduleVersionIdentifier, mainResolvers.getDependencyResolver(), mainResolvers.getArtifactResolver(), artifactType);
+    public LocallyAvailableExternalResource getMetaDataArtifact(ModuleComponentIdentifier moduleComponentIdentifier, ArtifactType artifactType) {
+        File resolvedArtifactFile = resolveMetaDataArtifactFile(moduleComponentIdentifier, mainResolvers.getComponentResolver(), mainResolvers.getArtifactResolver(), artifactType);
         LocallyAvailableResource localResource = new DefaultLocallyAvailableResource(resolvedArtifactFile);
         return new DefaultLocallyAvailableExternalResource(resolvedArtifactFile.toURI(), localResource);
     }
 
-    private File resolveMetaDataArtifactFile(ModuleVersionIdentifier moduleVersionIdentifier, DependencyToComponentResolver dependencyResolver,
+    private File resolveMetaDataArtifactFile(ModuleComponentIdentifier moduleComponentIdentifier, ComponentMetaDataResolver componentResolver,
                                              ArtifactResolver artifactResolver, ArtifactType artifactType) {
         BuildableComponentResolveResult moduleVersionResolveResult = new DefaultBuildableComponentResolveResult();
-        dependencyResolver.resolve(new DefaultDependencyMetaData(moduleVersionIdentifier), moduleVersionResolveResult);
+        componentResolver.resolve(moduleComponentIdentifier, new DefaultComponentOverrideMetadata(), moduleVersionResolveResult);
 
         BuildableArtifactSetResolveResult moduleArtifactsResolveResult = new DefaultBuildableArtifactSetResolveResult();
         artifactResolver.resolveModuleArtifacts(moduleVersionResolveResult.getMetaData(), artifactType, moduleArtifactsResolveResult);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java
index 4f26b35..cbf39de 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java
@@ -21,6 +21,7 @@ import org.gradle.internal.component.external.model.DefaultIvyModuleResolveMetaD
 import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData;
 import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
 import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData;
+import org.gradle.internal.component.model.*;
 import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepositoryAccess;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DescriptorParseContext;
@@ -28,15 +29,12 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.Downloaded
 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.internal.component.model.ConfigurationMetaData;
-import org.gradle.internal.component.model.DefaultIvyArtifactName;
-import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.component.model.IvyArtifactName;
 import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
 import org.gradle.internal.resource.local.FileStore;
 import org.gradle.internal.resource.local.LocallyAvailableResourceFinder;
 
 import java.net.URI;
+import java.util.Set;
 
 public class IvyResolver extends ExternalResourceResolver implements PatternBasedResolver {
 
@@ -100,8 +98,8 @@ public class IvyResolver extends ExternalResourceResolver implements PatternBase
         return new IvyRemoteRepositoryAccess();
     }
 
-    protected MutableModuleComponentResolveMetaData createMetaDataForDependency(DependencyMetaData dependency) {
-        return new DefaultIvyModuleResolveMetaData(dependency);
+    protected MutableModuleComponentResolveMetaData createDefaultComponentResolveMetaData(ModuleComponentIdentifier moduleComponentIdentifier, Set<IvyArtifactName> artifacts) {
+        return new DefaultIvyModuleResolveMetaData(moduleComponentIdentifier, artifacts);
     }
 
     protected MutableModuleComponentResolveMetaData parseMetaDataFromResource(ModuleComponentIdentifier moduleComponentIdentifier, LocallyAvailableExternalResource cachedResource, DescriptorParseContext context) {
@@ -112,7 +110,8 @@ public class IvyResolver extends ExternalResourceResolver implements PatternBase
 
     private class IvyLocalRepositoryAccess extends LocalRepositoryAccess {
 
-        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result) {
+        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ComponentUsage usage, BuildableArtifactSetResolveResult result) {
+            ConfigurationMetaData configuration = module.getConfiguration(usage.getConfigurationName());
             result.resolved(configuration.getArtifacts());
         }
 
@@ -135,7 +134,7 @@ public class IvyResolver extends ExternalResourceResolver implements PatternBase
 
     private class IvyRemoteRepositoryAccess extends RemoteRepositoryAccess {
         @Override
-        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result) {
+        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ComponentUsage usage, BuildableArtifactSetResolveResult result) {
             // Configuration artifacts are determined locally
         }
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
index b24e362..7cd1c2d 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
@@ -73,24 +73,24 @@ public class MavenResolver extends ExternalResourceResolver {
         return root;
     }
 
-    protected void doResolveComponentMetaData(DependencyMetaData dependency, ModuleComponentIdentifier moduleComponentIdentifier, BuildableModuleComponentMetaDataResolveResult result) {
+    protected void doResolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata prescribedMetaData, BuildableModuleComponentMetaDataResolveResult result) {
         if (isNonUniqueSnapshot(moduleComponentIdentifier)) {
             MavenUniqueSnapshotModuleSource uniqueSnapshotVersion = findUniqueSnapshotVersion(moduleComponentIdentifier, result);
             if (uniqueSnapshotVersion != null) {
                 MavenUniqueSnapshotComponentIdentifier snapshotIdentifier = composeSnapshotIdentifier(moduleComponentIdentifier, uniqueSnapshotVersion);
-                resolveUniqueSnapshotDependency(dependency, snapshotIdentifier, result, uniqueSnapshotVersion);
+                resolveUniqueSnapshotDependency(snapshotIdentifier, prescribedMetaData, result, uniqueSnapshotVersion);
                 return;
             }
         } else {
             MavenUniqueSnapshotModuleSource uniqueSnapshotVersion = composeUniqueSnapshotVersion(moduleComponentIdentifier);
             if (uniqueSnapshotVersion != null) {
                 MavenUniqueSnapshotComponentIdentifier snapshotIdentifier = composeSnapshotIdentifier(moduleComponentIdentifier, uniqueSnapshotVersion);
-                resolveUniqueSnapshotDependency(dependency, snapshotIdentifier, result, uniqueSnapshotVersion);
+                resolveUniqueSnapshotDependency(snapshotIdentifier, prescribedMetaData, result, uniqueSnapshotVersion);
                 return;
             }
         }
 
-        resolveStaticDependency(dependency, moduleComponentIdentifier, result, super.createArtifactResolver());
+        resolveStaticDependency(moduleComponentIdentifier, prescribedMetaData, result, super.createArtifactResolver());
     }
 
     protected boolean isMetaDataArtifact(ArtifactType artifactType) {
@@ -104,8 +104,8 @@ public class MavenResolver extends ExternalResourceResolver {
         return metaData;
     }
 
-    private void resolveUniqueSnapshotDependency(DependencyMetaData dependency, MavenUniqueSnapshotComponentIdentifier module, BuildableModuleComponentMetaDataResolveResult result, MavenUniqueSnapshotModuleSource snapshotSource) {
-        resolveStaticDependency(dependency, module, result, createArtifactResolver(snapshotSource));
+    private void resolveUniqueSnapshotDependency(MavenUniqueSnapshotComponentIdentifier module, ComponentOverrideMetadata prescribedMetaData, BuildableModuleComponentMetaDataResolveResult result, MavenUniqueSnapshotModuleSource snapshotSource) {
+        resolveStaticDependency(module, prescribedMetaData, result, createArtifactResolver(snapshotSource));
         if (result.getState() == BuildableModuleComponentMetaDataResolveResult.State.Resolved) {
             result.getMetaData().setSource(snapshotSource);
         }
@@ -191,8 +191,8 @@ public class MavenResolver extends ExternalResourceResolver {
     }
 
     @Override
-    protected MutableModuleComponentResolveMetaData createMetaDataForDependency(DependencyMetaData dependency) {
-        return processMetaData(new DefaultMavenModuleResolveMetaData(dependency));
+    protected MutableModuleComponentResolveMetaData createDefaultComponentResolveMetaData(ModuleComponentIdentifier moduleComponentIdentifier, Set<IvyArtifactName> artifacts) {
+        return processMetaData(new DefaultMavenModuleResolveMetaData(moduleComponentIdentifier, artifacts));
     }
 
     protected MutableModuleComponentResolveMetaData parseMetaDataFromResource(ModuleComponentIdentifier moduleComponentIdentifier, LocallyAvailableExternalResource cachedResource, DescriptorParseContext context) {
@@ -218,7 +218,7 @@ public class MavenResolver extends ExternalResourceResolver {
 
     private class MavenLocalRepositoryAccess extends LocalRepositoryAccess {
         @Override
-        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result) {
+        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ComponentUsage usage, BuildableArtifactSetResolveResult result) {
             if (mavenMetaData(module).isKnownJarPackaging()) {
                 ModuleComponentArtifactMetaData artifact = module.artifact("jar", "jar", null);
                 result.resolved(ImmutableSet.of(artifact));
@@ -238,7 +238,7 @@ public class MavenResolver extends ExternalResourceResolver {
 
     private class MavenRemoteRepositoryAccess extends RemoteRepositoryAccess {
         @Override
-        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ConfigurationMetaData configuration, BuildableArtifactSetResolveResult result) {
+        protected void resolveConfigurationArtifacts(ModuleComponentResolveMetaData module, ComponentUsage usage, BuildableArtifactSetResolveResult result) {
             MavenModuleResolveMetaData mavenMetaData = mavenMetaData(module);
             if (mavenMetaData.isPomPackaging()) {
                 Set<ComponentArtifactMetaData> artifacts = new LinkedHashSet<ComponentArtifactMetaData>();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/DependencyClassPathNotationConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/DependencyClassPathNotationConverter.java
index ee23175..bf3a772 100755
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/DependencyClassPathNotationConverter.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/DependencyClassPathNotationConverter.java
@@ -17,10 +17,10 @@ package org.gradle.api.internal.notations;
 
 import com.google.common.collect.Maps;
 import org.gradle.api.artifacts.SelfResolvingDependency;
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.internal.exceptions.DiagnosticsVisitor;
 import org.gradle.internal.reflect.Instantiator;
@@ -72,7 +72,7 @@ public class DependencyClassPathNotationConverter implements NotationConverter<D
         SelfResolvingDependency dependency = internCache.get(notation);
         if (dependency == null) {
             Collection<File> classpath = classPathRegistry.getClassPath(notation.name()).getAsFiles();
-            FileCollection files = fileResolver.resolveFiles(classpath);
+            FileCollectionInternal files = fileResolver.resolveFiles(classpath);
             dependency = instantiator.newInstance(DefaultSelfResolvingDependency.class, files);
             internCache.put(notation, dependency);
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentiferNotationConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentiferNotationConverter.java
deleted file mode 100644
index 183d68d..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentiferNotationConverter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.notations;
-
-import com.google.common.collect.Lists;
-import org.gradle.api.artifacts.ModuleIdentifier;
-import org.gradle.internal.exceptions.DiagnosticsVisitor;
-import org.gradle.internal.typeconversion.TypedNotationConverter;
-import org.gradle.internal.typeconversion.UnsupportedNotationException;
-
-import java.util.List;
-
-import static org.gradle.api.internal.artifacts.DefaultModuleIdentifier.newId;
-
-public class ModuleIdentiferNotationConverter extends TypedNotationConverter<String, ModuleIdentifier> {
-    private final static List<Character> INVALID_SPEC_CHARS = Lists.newArrayList('*', '[', ']', '(', ')', ',', '+');
-
-    public ModuleIdentiferNotationConverter() {
-        super(String.class);
-    }
-
-    /**
-     * Empty String for either group or module name is not allowed.
-     */
-    protected ModuleIdentifier parseType(String notation) {
-        assert notation != null;
-        String[] split = notation.split(":");
-        if (split.length != 2) {
-            throw new UnsupportedNotationException(notation);
-        }
-        String group = split[0].trim();
-        String name = split[1].trim();
-        if (group.length() == 0 || name.length() == 0) {
-            throw new UnsupportedNotationException(notation);
-        }
-
-        for (char c : INVALID_SPEC_CHARS) {
-            if (group.indexOf(c) != -1 || name.indexOf(c) != -1) {
-                throw new UnsupportedNotationException(notation);
-            }
-        }
-
-        return newId(group, name);
-    }
-
-    @Override
-    public void describe(DiagnosticsVisitor visitor) {
-        visitor.candidate("String describing the module in 'group:name' format").example("'org.gradle:gradle-core'.");
-    }
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentifierNotationConverter.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentifierNotationConverter.java
new file mode 100644
index 0000000..a002fc0
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/notations/ModuleIdentifierNotationConverter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.notations;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.internal.exceptions.DiagnosticsVisitor;
+import org.gradle.internal.typeconversion.TypedNotationConverter;
+import org.gradle.internal.typeconversion.UnsupportedNotationException;
+import org.gradle.util.GUtil;
+
+import java.util.List;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleIdentifier.newId;
+
+public class ModuleIdentifierNotationConverter extends TypedNotationConverter<String, ModuleIdentifier> {
+    private final static List<Character> INVALID_SPEC_CHARS = Lists.newArrayList('*', '[', ']', '(', ')', ',', '+');
+
+    public ModuleIdentifierNotationConverter() {
+        super(String.class);
+    }
+
+    /**
+     * Empty String for either group or module name is not allowed.
+     */
+    protected ModuleIdentifier parseType(String notation) {
+        assert notation != null;
+        String[] split = notation.split(":");
+        if (split.length != 2) {
+            throw new UnsupportedNotationException(notation);
+        }
+        String group = validate(split[0].trim(), notation);
+        String name = validate(split[1].trim(), notation);
+        return newId(group, name);
+    }
+
+    public static String validate(String part, String notation) {
+        if (!GUtil.isTrue(part)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        for (char c : INVALID_SPEC_CHARS) {
+            if (part.indexOf(c) != -1) {
+                throw new UnsupportedNotationException(notation);
+            }
+        }
+        return part;
+    }
+
+    @Override
+    public void describe(DiagnosticsVisitor visitor) {
+        visitor.candidate("String describing the module in 'group:name' format").example("'org.gradle:gradle-core'.");
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaData.java
index 02ee441..020d95b 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaData.java
@@ -19,26 +19,19 @@ package org.gradle.internal.component.external.model;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.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.internal.component.model.AbstractModuleDescriptorBackedMetaData;
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-import org.gradle.internal.component.model.ConfigurationMetaData;
-import org.gradle.internal.component.model.ModuleSource;
+import org.gradle.internal.component.model.*;
 
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 
 abstract class AbstractModuleComponentResolveMetaData extends AbstractModuleDescriptorBackedMetaData implements MutableModuleComponentResolveMetaData {
-    private Set<ModuleComponentArtifactMetaData> artifacts;
     private Multimap<String, ModuleComponentArtifactMetaData> artifactsByConfig;
 
     public AbstractModuleComponentResolveMetaData(ModuleDescriptor moduleDescriptor) {
@@ -59,7 +52,6 @@ abstract class AbstractModuleComponentResolveMetaData extends AbstractModuleDesc
 
     protected void copyTo(AbstractModuleComponentResolveMetaData copy) {
         super.copyTo(copy);
-        copy.artifacts = artifacts;
         copy.artifactsByConfig = artifactsByConfig;
     }
 
@@ -82,25 +74,12 @@ abstract class AbstractModuleComponentResolveMetaData extends AbstractModuleDesc
         setId(DefaultModuleVersionIdentifier.newId(componentId));
     }
 
-    public ModuleComponentArtifactMetaData artifact(Artifact artifact) {
-        return new DefaultModuleComponentArtifactMetaData(getComponentId(), artifact);
-    }
-
     public ModuleComponentArtifactMetaData 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, extension, extraAttributes);
-        return new DefaultModuleComponentArtifactMetaData(getComponentId(), artifact);
-    }
-
-    public Set<ModuleComponentArtifactMetaData> getArtifacts() {
-        if (artifacts == null) {
-            populateArtifactsFromDescriptor();
-        }
-        return artifacts;
+        IvyArtifactName ivyArtifactName = new DefaultIvyArtifactName(getId().getName(), type, extension, classifier);
+        return new DefaultModuleComponentArtifactMetaData(getComponentId(), ivyArtifactName);
     }
 
     public void setArtifacts(Iterable<? extends ModuleComponentArtifactMetaData> artifacts) {
-        this.artifacts = Sets.newLinkedHashSet(artifacts);
         this.artifactsByConfig = LinkedHashMultimap.create();
         for (String config : getDescriptor().getConfigurationsNames()) {
             artifactsByConfig.putAll(config, artifacts);
@@ -121,12 +100,11 @@ abstract class AbstractModuleComponentResolveMetaData extends AbstractModuleDesc
     private void populateArtifactsFromDescriptor() {
         Map<Artifact, ModuleComponentArtifactMetaData> artifactToMetaData = Maps.newLinkedHashMap();
         for (Artifact descriptorArtifact : getDescriptor().getAllArtifacts()) {
-            ModuleComponentArtifactMetaData artifact = artifact(descriptorArtifact);
+            IvyArtifactName artifactName = DefaultIvyArtifactName.forIvyArtifact(descriptorArtifact);
+            ModuleComponentArtifactMetaData artifact = new DefaultModuleComponentArtifactMetaData(getComponentId(), artifactName);
             artifactToMetaData.put(descriptorArtifact, artifact);
         }
 
-        artifacts = Sets.newLinkedHashSet(artifactToMetaData.values());
-
         this.artifactsByConfig = LinkedHashMultimap.create();
         for (String configuration : getDescriptor().getConfigurationsNames()) {
             Artifact[] configArtifacts = getDescriptor().getArtifacts(configuration);
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModulePublishMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModulePublishMetaData.java
index 8f3a6b4..ae35b03 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModulePublishMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModulePublishMetaData.java
@@ -17,11 +17,23 @@
 package org.gradle.internal.component.external.model;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.internal.component.local.model.LocalConfigurationMetaData;
+import org.gradle.internal.component.model.DependencyMetaData;
 
 import java.io.File;
 
 public interface BuildableIvyModulePublishMetaData extends IvyModulePublishMetaData {
+    void addConfiguration(LocalConfigurationMetaData configuration);
+
+    void addExcludeRule(ExcludeRule excludeRule);
+
+    void addDependency(DependencyMetaData dependency);
+
     void addArtifact(IvyModuleArtifactPublishMetaData artifact);
 
     void addArtifact(Artifact artifact, File file);
+
+    void addArtifact(String configuration, PublishArtifact publishArtifact);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModuleResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModuleResolveMetaData.java
index 43453a3..03a3481 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModuleResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/BuildableIvyModuleResolveMetaData.java
@@ -60,7 +60,7 @@ public class BuildableIvyModuleResolveMetaData extends DefaultIvyModuleResolveMe
     }
 
     private boolean artifactsEqual(Artifact a, Artifact b) {
-        return new DefaultIvyArtifactName(a).equals(new DefaultIvyArtifactName(b));
+        return DefaultIvyArtifactName.forIvyArtifact(a).equals(DefaultIvyArtifactName.forIvyArtifact(b));
     }
 
     private static void attachArtifact(MDArtifact artifact, Set<String> configurations, DefaultModuleDescriptor target) {
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaData.java
index 27de043..3f3b849 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaData.java
@@ -16,27 +16,86 @@
 
 package org.gradle.internal.component.external.model;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.internal.component.local.model.LocalConfigurationMetaData;
+import org.gradle.internal.component.model.DefaultIvyArtifactName;
+import org.gradle.internal.component.model.DependencyMetaData;
 import org.gradle.internal.component.model.IvyArtifactName;
+import org.gradle.util.WrapUtil;
 
 import java.io.File;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
 
 public class DefaultIvyModulePublishMetaData implements BuildableIvyModulePublishMetaData {
     private final ModuleVersionIdentifier id;
+    private final DefaultModuleDescriptor moduleDescriptor;
     private final Map<ModuleComponentArtifactIdentifier, IvyModuleArtifactPublishMetaData> artifactsById = new LinkedHashMap<ModuleComponentArtifactIdentifier, IvyModuleArtifactPublishMetaData>();
 
-    public DefaultIvyModulePublishMetaData(ModuleVersionIdentifier id) {
+    public DefaultIvyModulePublishMetaData(ModuleVersionIdentifier id, String status) {
         this.id = id;
+        moduleDescriptor = new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(id), status, null);
+        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+    }
+
+    public DefaultIvyModulePublishMetaData(ModuleVersionIdentifier id, ModuleDescriptor moduleDescriptor) {
+        this.id = id;
+        this.moduleDescriptor = (DefaultModuleDescriptor) moduleDescriptor;
     }
 
     public ModuleVersionIdentifier getId() {
         return id;
     }
 
+    public DefaultModuleDescriptor getModuleDescriptor() {
+        return moduleDescriptor;
+    }
+
+    @Override
+    public void addConfiguration(LocalConfigurationMetaData configuration) {
+        Set<String> extendsFrom = configuration.getExtendsFrom();
+        String[] superConfigs = extendsFrom.toArray(new String[extendsFrom.size()]);
+        Arrays.sort(superConfigs);
+        Configuration.Visibility visibility = configuration.isVisible() ? Configuration.Visibility.PUBLIC : Configuration.Visibility.PRIVATE;
+        Configuration conf = new Configuration(configuration.getName(), visibility, configuration.getDescription(), superConfigs, configuration.isTransitive(), null);
+        moduleDescriptor.addConfiguration(conf);
+    }
+
+    @Override
+    public void addExcludeRule(ExcludeRule excludeRule) {
+        moduleDescriptor.addExcludeRule(excludeRule);
+    }
+
+    @Override
+    public void addDependency(DependencyMetaData dependency) {
+        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(dependency.getRequested().getGroup(), dependency.getRequested().getName(), dependency.getRequested().getVersion());
+        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(moduleDescriptor, moduleRevisionId, dependency.isForce(), dependency.isChanging(), dependency.isTransitive());
+
+        // In reality, there will only be 1 module configuration and 1 matching dependency configuration
+        for (String moduleConfiguration : dependency.getModuleConfigurations()) {
+            for (String dependencyConfiguration : dependency.getDependencyConfigurations(moduleConfiguration, moduleConfiguration)) {
+                dependencyDescriptor.addDependencyConfiguration(moduleConfiguration, dependencyConfiguration);
+            }
+            addDependencyArtifacts(moduleConfiguration, dependency.getArtifacts(), dependencyDescriptor);
+        }
+
+        moduleDescriptor.addDependency(dependencyDescriptor);
+    }
+
+    private void addDependencyArtifacts(String configuration, Set<IvyArtifactName> artifacts, DefaultDependencyDescriptor dependencyDescriptor) {
+        for (IvyArtifactName artifact : artifacts) {
+            DefaultDependencyArtifactDescriptor artifactDescriptor = new DefaultDependencyArtifactDescriptor(
+                    dependencyDescriptor, artifact.getName(), artifact.getType(), artifact.getExtension(),
+                    null,
+                    artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER, artifact.getClassifier()) : null);
+            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
+        }
+    }
+
     public void addArtifact(Artifact artifact, File file) {
         DefaultIvyModuleArtifactPublishMetaData publishMetaData = new DefaultIvyModuleArtifactPublishMetaData(id, artifact, file);
         artifactsById.put(publishMetaData.getId(), publishMetaData);
@@ -46,12 +105,31 @@ public class DefaultIvyModulePublishMetaData implements BuildableIvyModulePublis
         artifactsById.put(artifact.getId(), artifact);
     }
 
-    public Collection<IvyModuleArtifactPublishMetaData> getArtifacts() {
-        return artifactsById.values();
+    @Override
+    public void addArtifact(String configuration, PublishArtifact publishArtifact) {
+        MDArtifact artifact = getOrCreate(DefaultIvyArtifactName.forPublishArtifact(publishArtifact, getId().getName()));
+        artifact.addConfiguration(configuration);
+        addArtifact(artifact, publishArtifact.getFile());
     }
 
-    public IvyModuleArtifactPublishMetaData getArtifact(ModuleComponentArtifactIdentifier artifactIdentifier) {
-        return artifactsById.get(artifactIdentifier);
+    private MDArtifact getOrCreate(IvyArtifactName ivyName) {
+        for (IvyModuleArtifactPublishMetaData artifactPublishMetaData : artifactsById.values()) {
+            if (artifactPublishMetaData.getArtifactName().equals(ivyName)) {
+                return (MDArtifact) artifactPublishMetaData.toIvyArtifact();
+            }
+        }
+        return new MDArtifact(moduleDescriptor, ivyName.getName(), ivyName.getType(), ivyName.getExtension(), null, ivyArtifactAttributes(ivyName));
+    }
+
+    private Map<String, String> ivyArtifactAttributes(IvyArtifactName ivyArtifactName) {
+        if (ivyArtifactName.getClassifier() == null) {
+            return Collections.emptyMap();
+        }
+        return Collections.singletonMap("m:classifier", ivyArtifactName.getClassifier());
+    }
+
+    public Collection<IvyModuleArtifactPublishMetaData> getArtifacts() {
+        return artifactsById.values();
     }
 
     private static class DefaultIvyModuleArtifactPublishMetaData implements IvyModuleArtifactPublishMetaData {
@@ -60,7 +138,7 @@ public class DefaultIvyModulePublishMetaData implements BuildableIvyModulePublis
         private final File file;
 
         private DefaultIvyModuleArtifactPublishMetaData(ModuleVersionIdentifier moduleVersionIdentifier, Artifact artifact, File file) {
-            this.id = new DefaultModuleComponentArtifactIdentifier(DefaultModuleComponentIdentifier.newId(moduleVersionIdentifier), artifact);
+            this.id = new DefaultModuleComponentArtifactIdentifier(DefaultModuleComponentIdentifier.newId(moduleVersionIdentifier), DefaultIvyArtifactName.forIvyArtifact(artifact));
             this.artifact = artifact;
             this.file = file;
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModuleResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModuleResolveMetaData.java
index 78cf6fc..803a5b3 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModuleResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultIvyModuleResolveMetaData.java
@@ -17,13 +17,14 @@ package org.gradle.internal.component.external.model;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.NamespaceId;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.NamespaceId;
+import org.gradle.internal.component.model.IvyArtifactName;
 
 import java.util.Map;
+import java.util.Set;
 
 public class DefaultIvyModuleResolveMetaData extends AbstractModuleComponentResolveMetaData implements IvyModuleResolveMetaData {
     private final Map<NamespaceId, String> extraInfo;
@@ -43,8 +44,8 @@ public class DefaultIvyModuleResolveMetaData extends AbstractModuleComponentReso
         this.extraInfo = moduleDescriptor.getExtraInfo();
     }
 
-    public DefaultIvyModuleResolveMetaData(DependencyMetaData dependencyMetaData) {
-        this(IvyUtil.createModuleDescriptor(dependencyMetaData.getDescriptor()));
+    public DefaultIvyModuleResolveMetaData(ModuleComponentIdentifier componentIdentifier, Set<IvyArtifactName> artifacts) {
+        this(componentIdentifier, IvyUtil.createModuleDescriptor(componentIdentifier, artifacts));
     }
 
     @Override
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetaData.java
index 9897a0c..6c78ce4 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetaData.java
@@ -22,10 +22,11 @@ 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.ivyservice.IvyUtil;
-import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.model.IvyArtifactName;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Set;
 
 public class DefaultMavenModuleResolveMetaData extends AbstractModuleComponentResolveMetaData implements MavenModuleResolveMetaData {
     private static final String POM_PACKAGING = "pom";
@@ -52,8 +53,8 @@ public class DefaultMavenModuleResolveMetaData extends AbstractModuleComponentRe
         this.relocated = relocated;
     }
 
-    public DefaultMavenModuleResolveMetaData(DependencyMetaData dependencyMetaData) {
-        this(IvyUtil.createModuleDescriptor(dependencyMetaData.getDescriptor()), "jar", false);
+    public DefaultMavenModuleResolveMetaData(ModuleComponentIdentifier componentIdentifier, Set<IvyArtifactName> artifacts) {
+        this(componentIdentifier, IvyUtil.createModuleDescriptor(componentIdentifier, artifacts), "jar", false);
     }
 
     @Override
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactIdentifier.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactIdentifier.java
index 3241ad9..4b1a405 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactIdentifier.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactIdentifier.java
@@ -16,7 +16,6 @@
 
 package org.gradle.internal.component.external.model;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.model.DefaultIvyArtifactName;
@@ -29,10 +28,6 @@ public class DefaultModuleComponentArtifactIdentifier implements ModuleComponent
     private final ModuleComponentIdentifier componentIdentifier;
     private final IvyArtifactName name;
 
-    public DefaultModuleComponentArtifactIdentifier(ModuleComponentIdentifier componentIdentifier, Artifact artifact) {
-        this(componentIdentifier, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttributes());
-    }
-
     public DefaultModuleComponentArtifactIdentifier(ModuleComponentIdentifier componentIdentifier, String name, String type, @Nullable String extension) {
         this(componentIdentifier, name, type, extension, Collections.<String, String>emptyMap());
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaData.java
index 1e2c000..1361259 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaData.java
@@ -16,22 +16,15 @@
 
 package org.gradle.internal.component.external.model;
 
-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;
 import org.gradle.internal.component.model.IvyArtifactName;
 
 public class DefaultModuleComponentArtifactMetaData implements ModuleComponentArtifactMetaData {
     private final DefaultModuleComponentArtifactIdentifier id;
 
-    public DefaultModuleComponentArtifactMetaData(ModuleComponentIdentifier componentIdentifier, Artifact artifact) {
-        this(new DefaultModuleComponentArtifactIdentifier(componentIdentifier, artifact));
-    }
-
     public DefaultModuleComponentArtifactMetaData(ModuleComponentIdentifier componentIdentifier, IvyArtifactName artifact) {
         this(new DefaultModuleComponentArtifactIdentifier(componentIdentifier, artifact));
     }
@@ -57,11 +50,6 @@ public class DefaultModuleComponentArtifactMetaData implements ModuleComponentAr
         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/dependency-management/src/main/java/org/gradle/internal/component/external/model/IvyModulePublishMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/IvyModulePublishMetaData.java
index 8a64377..242cf7e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/IvyModulePublishMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/IvyModulePublishMetaData.java
@@ -16,11 +16,17 @@
 
 package org.gradle.internal.component.external.model;
 
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 
 import java.util.Collection;
 
 public interface IvyModulePublishMetaData {
+    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
+    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
+
+    ModuleDescriptor getModuleDescriptor();
+
     ModuleVersionIdentifier getId();
 
     Collection<IvyModuleArtifactPublishMetaData> getArtifacts();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentArtifactMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentArtifactMetaData.java
index e1a5dff..4b939f2 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentArtifactMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentArtifactMetaData.java
@@ -16,7 +16,6 @@
 
 package org.gradle.internal.component.external.model;
 
-import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.artifacts.ArtifactIdentifier;
 import org.gradle.internal.component.model.ComponentArtifactMetaData;
 
@@ -27,11 +26,6 @@ public interface ModuleComponentArtifactMetaData extends ComponentArtifactMetaDa
     ModuleComponentArtifactIdentifier 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
      */
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentResolveMetaData.java
index 0bfd60b..71686bb 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ModuleComponentResolveMetaData.java
@@ -15,14 +15,12 @@
  */
 package org.gradle.internal.component.external.model;
 
-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.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
 import org.gradle.internal.component.model.ModuleSource;
 
-import java.util.Set;
-
 /**
  * The meta-data for a module version that is required during dependency resolution.
  */
@@ -31,10 +29,13 @@ public interface ModuleComponentResolveMetaData extends ComponentResolveMetaData
 
     ModuleComponentResolveMetaData withSource(ModuleSource source);
 
-    Set<ModuleComponentArtifactMetaData> getArtifacts();
-
-    ModuleComponentArtifactMetaData artifact(Artifact artifact);
-
     ModuleComponentArtifactMetaData artifact(String type, @Nullable String extension, @Nullable String classifier);
 
+    /**
+     * 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.
+     *
+     * <p>You should avoid using this method.
+     */
+    ModuleDescriptor getDescriptor();
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifier.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifier.java
new file mode 100644
index 0000000..836d626
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.component.local.model;
+
+import com.google.common.base.Objects;
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier;
+
+public class DefaultLibraryComponentIdentifier implements LibraryComponentIdentifier {
+    private final String projectPath;
+    private final String libraryName;
+    private final String displayName;
+
+    public static String libraryToConfigurationName(String projectPath, String libraryName) {
+        return String.format("project %s library %s", projectPath, libraryName);
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public DefaultLibraryComponentIdentifier(String projectPath, String libraryName) {
+        assert projectPath != null : "project path cannot be null";
+        assert libraryName != null : "library name cannot be null";
+        this.projectPath = projectPath;
+        this.libraryName = libraryName;
+        this.displayName = libraryToConfigurationName(projectPath, libraryName);
+    }
+
+    @Override
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    @Override
+    public String getLibraryName() {
+        return libraryName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DefaultLibraryComponentIdentifier that = (DefaultLibraryComponentIdentifier) o;
+        return Objects.equal(projectPath, that.projectPath)
+            && Objects.equal(libraryName, that.libraryName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(projectPath, libraryName);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentSelector.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentSelector.java
new file mode 100644
index 0000000..84e7543
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLibraryComponentSelector.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.local.model;
+
+import com.google.common.base.Objects;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier;
+import org.gradle.api.artifacts.component.LibraryComponentSelector;
+
+public class DefaultLibraryComponentSelector implements LibraryComponentSelector {
+    private final String projectPath;
+    private final String libraryName;
+
+    public DefaultLibraryComponentSelector(String projectPath, String libraryName) {
+        assert projectPath != null : "project path cannot be null";
+        assert libraryName != null : "library name cannot be null";
+        this.projectPath = projectPath;
+        this.libraryName = libraryName;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return DefaultLibraryComponentIdentifier.libraryToConfigurationName(getProjectPath(), getLibraryName());
+    }
+
+    @Override
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    @Override
+    public String getLibraryName() {
+        return libraryName;
+    }
+
+    public boolean matchesStrictly(ComponentIdentifier identifier) {
+        assert identifier != null : "identifier cannot be null";
+
+        if (identifier instanceof LibraryComponentIdentifier) {
+            LibraryComponentIdentifier projectComponentIdentifier = (LibraryComponentIdentifier) identifier;
+            return projectPath.equals(projectComponentIdentifier.getProjectPath()) && libraryName.equals(projectComponentIdentifier.getLibraryName());
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DefaultLibraryComponentSelector that = (DefaultLibraryComponentSelector) o;
+        return Objects.equal(projectPath, that.projectPath)
+            && Objects.equal(libraryName, that.libraryName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(projectPath, libraryName);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifier.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifier.java
deleted file mode 100644
index e80a00d..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifier.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.internal.component.local.model;
-
-import org.gradle.api.Nullable;
-import org.gradle.api.artifacts.component.ComponentIdentifier;
-import org.gradle.internal.component.model.ComponentArtifactIdentifier;
-import org.gradle.internal.component.model.DefaultIvyArtifactName;
-import org.gradle.internal.component.model.IvyArtifactName;
-
-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)", name, componentDisplayName);
-    }
-
-    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/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetaData.java
index cd8b39b..7e5ddc9 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetaData.java
@@ -16,167 +16,271 @@
 
 package org.gradle.internal.component.local.model;
 
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import org.apache.ivy.core.module.descriptor.*;
-import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.component.ComponentIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData;
-import org.gradle.internal.component.model.ModuleSource;
 import org.gradle.internal.component.external.model.BuildableIvyModulePublishMetaData;
 import org.gradle.internal.component.external.model.DefaultIvyModulePublishMetaData;
 import org.gradle.internal.component.model.*;
 
-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> artifactsByIvyId = new LinkedHashMap<ArtifactRevisionId, DefaultLocalArtifactMetaData>();
-    private final Multimap<String, DefaultLocalArtifactMetaData> artifactsByConfig = LinkedHashMultimap.create();
-    private final List<DependencyMetaData> dependencies = new ArrayList<DependencyMetaData>();
-    private final DefaultModuleDescriptor moduleDescriptor;
+    private final Map<String, DefaultLocalConfigurationMetaData> allConfigurations = Maps.newHashMap();
+    private final Map<String, Iterable<? extends PublishArtifact>> allArtifacts = Maps.newHashMap();
+    private final List<DependencyMetaData> allDependencies = Lists.newArrayList();
+    private final List<ExcludeRule> allExcludeRules = Lists.newArrayList();
     private final ModuleVersionIdentifier id;
     private final ComponentIdentifier componentIdentifier;
+    private final String status;
 
-    public DefaultLocalComponentMetaData(DefaultModuleDescriptor moduleDescriptor, ComponentIdentifier componentIdentifier) {
-        this.moduleDescriptor = moduleDescriptor;
-        id = DefaultModuleVersionIdentifier.newId(moduleDescriptor.getModuleRevisionId());
+    public DefaultLocalComponentMetaData(ModuleVersionIdentifier id, ComponentIdentifier componentIdentifier, String status) {
+        this.id = id;
         this.componentIdentifier = componentIdentifier;
+        this.status = status;
     }
 
     public ModuleVersionIdentifier getId() {
         return id;
     }
 
-    public DefaultModuleDescriptor getModuleDescriptor() {
-        return moduleDescriptor;
+    public void addArtifacts(String configuration, Iterable<? extends PublishArtifact> artifacts) {
+        allArtifacts.put(configuration, artifacts);
     }
 
-    public void addArtifact(String configuration, IvyArtifactName artifact, File file) {
-        Artifact ivyArtifact = new MDArtifact(moduleDescriptor, artifact.getName(), artifact.getType(), artifact.getExtension(), null, artifact.getAttributes());
-
-        DefaultLocalArtifactMetaData artifactMetaData = new DefaultLocalArtifactMetaData(componentIdentifier, id.toString(), ivyArtifact, file);
-        if (artifactsById.containsKey(artifactMetaData.getId())) {
-            artifactMetaData = artifactsById.get(artifactMetaData.getId());
-        } else {
-            artifactsById.put(artifactMetaData.id, artifactMetaData);
-            artifactsByIvyId.put(ivyArtifact.getId(), artifactMetaData);
-        }
-        moduleDescriptor.addArtifact(configuration, ivyArtifact);
-        artifactsByConfig.put(configuration, artifactMetaData);
-        ((MDArtifact) artifactMetaData.artifact).addConfiguration(configuration);
-    }
-
-    public void addConfiguration(String name, boolean visible, String description, String[] superConfigs, boolean transitive) {
-        moduleDescriptor.addConfiguration(new Configuration(name, visible ? Configuration.Visibility.PUBLIC : Configuration.Visibility.PRIVATE, description, superConfigs, transitive, null));
+    public void addConfiguration(String name, String description, Set<String> extendsFrom, Set<String> hierarchy, boolean visible, boolean transitive) {
+        DefaultLocalConfigurationMetaData conf = new DefaultLocalConfigurationMetaData(name, description, visible, transitive, extendsFrom, hierarchy);
+        allConfigurations.put(name, conf);
     }
 
     public void addDependency(DependencyMetaData dependency) {
-        dependencies.add(dependency);
-        moduleDescriptor.addDependency(dependency.getDescriptor());
+        allDependencies.add(dependency);
     }
 
     public void addExcludeRule(ExcludeRule excludeRule) {
-        moduleDescriptor.addExcludeRule(excludeRule);
-    }
-
-    public Collection<? extends LocalArtifactMetaData> getArtifacts() {
-        return artifactsById.values();
-    }
-
-    public LocalArtifactMetaData getArtifact(ComponentArtifactIdentifier artifactIdentifier) {
-        return artifactsById.get(artifactIdentifier);
+        allExcludeRules.add(excludeRule);
     }
 
     public ComponentResolveMetaData toResolveMetaData() {
-        return new LocalComponentResolveMetaData();
+        return new DefaultLocalComponentResolveMetaData();
     }
 
     public BuildableIvyModulePublishMetaData toPublishMetaData() {
-        DefaultIvyModulePublishMetaData publishMetaData = new DefaultIvyModulePublishMetaData(id);
-        for (DefaultLocalArtifactMetaData artifact : artifactsById.values()) {
-            publishMetaData.addArtifact(artifact.artifact, artifact.file);
+        DefaultIvyModulePublishMetaData publishMetaData = new DefaultIvyModulePublishMetaData(id, status);
+        for (DefaultLocalConfigurationMetaData configuration : allConfigurations.values()) {
+            publishMetaData.addConfiguration(configuration);
+        }
+        for (ExcludeRule excludeRule : allExcludeRules) {
+            publishMetaData.addExcludeRule(excludeRule);
+        }
+        for (DependencyMetaData dependency : allDependencies) {
+            publishMetaData.addDependency(dependency);
+        }
+        for (String configuration : allArtifacts.keySet()) {
+            Iterable<? extends PublishArtifact> publishArtifacts = allArtifacts.get(configuration);
+            for (PublishArtifact publishArtifact : publishArtifacts) {
+                publishMetaData.addArtifact(configuration, publishArtifact);
+            }
         }
         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 class DefaultLocalComponentResolveMetaData implements ComponentResolveMetaData {
+        private ModuleVersionIdentifier moduleVersionIdentifier;
 
-        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;
+        public DefaultLocalComponentResolveMetaData() {
+            this.moduleVersionIdentifier = id;
         }
 
         @Override
         public String toString() {
-            return id.toString();
+            return componentIdentifier.getDisplayName();
+        }
+
+        public ModuleVersionIdentifier getId() {
+            return moduleVersionIdentifier;
         }
 
-        public IvyArtifactName getName() {
-            return id.getName();
+        public ModuleSource getSource() {
+            return null;
+        }
+
+        public ComponentResolveMetaData withSource(ModuleSource source) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isGenerated() {
+            return false;
+        }
+
+        public boolean isChanging() {
+            return false;
+        }
+
+        public String getStatus() {
+            return status;
+        }
+
+        public List<String> getStatusScheme() {
+            return DEFAULT_STATUS_SCHEME;
         }
 
         public ComponentIdentifier getComponentId() {
             return componentIdentifier;
         }
 
-        public ComponentArtifactIdentifier getId() {
-            return id;
+        public List<DependencyMetaData> getDependencies() {
+            return allDependencies;
         }
 
-        public File getFile() {
-            return file;
+        @Override
+        public Set<String> getConfigurationNames() {
+            return allConfigurations.keySet();
         }
-    }
 
-    private class LocalComponentResolveMetaData extends AbstractModuleDescriptorBackedMetaData {
-        public LocalComponentResolveMetaData() {
-            // TODO:ADAM - need to clone the descriptor
-            super(id, moduleDescriptor, componentIdentifier);
+        public DefaultLocalConfigurationMetaData getConfiguration(final String name) {
+            return allConfigurations.get(name);
         }
 
-        public ModuleComponentResolveMetaData withSource(ModuleSource source) {
-            throw new UnsupportedOperationException();
+    }
+
+    private class DefaultLocalConfigurationMetaData implements LocalConfigurationMetaData {
+        private final String name;
+        private final String description;
+        private final boolean transitive;
+        private final boolean visible;
+        private final Set<String> hierarchy;
+        private final Set<String> extendsFrom;
+
+        private List<DependencyMetaData> configurationDependencies;
+        private LinkedHashSet<ExcludeRule> configurationExcludeRules;
+
+        private DefaultLocalConfigurationMetaData(String name, String description, boolean visible, boolean transitive, Set<String> extendsFrom, Set<String> hierarchy) {
+            this.name = name;
+            this.description = description;
+            this.transitive = transitive;
+            this.visible = visible;
+            this.hierarchy = hierarchy;
+            this.extendsFrom = extendsFrom;
         }
 
         @Override
-        protected List<DependencyMetaData> populateDependenciesFromDescriptor() {
-            return dependencies;
+        public String toString() {
+            return String.format("%s:%s", componentIdentifier.getDisplayName(), name);
+        }
+
+        public ComponentResolveMetaData getComponent() {
+            return new DefaultLocalComponentResolveMetaData();
         }
 
-        public ComponentArtifactMetaData artifact(Artifact artifact) {
-            DefaultLocalArtifactMetaData candidate = artifactsByIvyId.get(artifact.getId());
-            return candidate != null ? candidate : new DefaultLocalArtifactMetaData(componentIdentifier, id.toString(), artifact, null);
+        public String getDescription() {
+            return description;
+        }
+
+        public Set<String> getExtendsFrom() {
+            return extendsFrom;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Set<String> getHierarchy() {
+            return hierarchy;
+        }
+
+        public boolean isTransitive() {
+            return transitive;
+        }
+
+        public boolean isVisible() {
+            return visible;
+        }
+
+        public List<DependencyMetaData> getDependencies() {
+            if (configurationDependencies == null) {
+                configurationDependencies = new ArrayList<DependencyMetaData>();
+                for (DependencyMetaData dependency : allDependencies) {
+                    if (include(dependency)) {
+                        configurationDependencies.add(dependency);
+                    }
+                }
+            }
+            return configurationDependencies;
+        }
+
+        private boolean include(DependencyMetaData dependency) {
+            String[] moduleConfigurations = dependency.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 (configurationExcludeRules == null) {
+                configurationExcludeRules = new LinkedHashSet<ExcludeRule>();
+                for (ExcludeRule excludeRule : allExcludeRules) {
+                    for (String config : excludeRule.getConfigurations()) {
+                        if (hierarchy.contains(config)) {
+                            configurationExcludeRules.add(excludeRule);
+                            break;
+                        }
+                    }
+                }
+            }
+            return configurationExcludeRules;
         }
 
         public Set<ComponentArtifactMetaData> getArtifacts() {
-            return new LinkedHashSet<ComponentArtifactMetaData>(artifactsById.values());
+            return DefaultLocalComponentMetaData.getArtifacts(componentIdentifier, id, getHierarchy(), allArtifacts);
         }
 
-        @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);
+        public ComponentArtifactMetaData artifact(IvyArtifactName ivyArtifactName) {
+            for (ComponentArtifactMetaData candidate : getArtifacts()) {
+                if (candidate.getName().equals(ivyArtifactName)) {
+                    return candidate;
+                }
+            }
+
+            return new MissingLocalArtifactMetaData(componentIdentifier, id.toString(), ivyArtifactName);
+        }
+    }
+
+    static Set<ComponentArtifactMetaData> getArtifacts(ComponentIdentifier componentIdentifier, ModuleVersionIdentifier moduleVersionIdentifier,
+                                                       Set<String> configurationHierarchy, Map<String, Iterable<? extends PublishArtifact>> allArtifacts) {
+        Set<PublishArtifact> seen = Sets.newHashSet();
+        Set<ComponentArtifactMetaData> artifacts = Sets.newLinkedHashSet();
+
+        for (String config : configurationHierarchy) {
+            Iterable<? extends PublishArtifact> publishArtifacts = allArtifacts.get(config);
+            if (publishArtifacts != null) {
+                for (PublishArtifact publishArtifact : publishArtifacts) {
+                    if (seen.add(publishArtifact)) {
+                        artifacts.add(new PublishArtifactLocalArtifactMetaData(componentIdentifier, componentIdentifier.getDisplayName(), moduleVersionIdentifier.getName(), publishArtifact));
                     }
                 }
             }
-            return result;
         }
+        return artifacts;
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DslOriginDependencyMetaDataWrapper.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DslOriginDependencyMetaDataWrapper.java
index f619016..0ebb22a 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DslOriginDependencyMetaDataWrapper.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DslOriginDependencyMetaDataWrapper.java
@@ -16,12 +16,16 @@
 
 package org.gradle.internal.component.local.model;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.component.ComponentSelector;
-import org.gradle.internal.component.model.*;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.ConfigurationMetaData;
+import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.model.IvyArtifactName;
 
+import java.util.Collection;
 import java.util.Set;
 
 public class DslOriginDependencyMetaDataWrapper implements DslOriginDependencyMetaData {
@@ -41,8 +45,22 @@ public class DslOriginDependencyMetaDataWrapper implements DslOriginDependencyMe
         return delegate.getRequested();
     }
 
-    public DependencyDescriptor getDescriptor() {
-        return delegate.getDescriptor();
+    @Override
+    public String[] getModuleConfigurations() {
+        return delegate.getModuleConfigurations();
+    }
+
+    @Override
+    public String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration) {
+        return delegate.getDependencyConfigurations(moduleConfiguration, requestedConfiguration);
+    }
+
+    public ExcludeRule[] getExcludeRules(Collection<String> configurations) {
+        return delegate.getExcludeRules(configurations);
+    }
+
+    public String getDynamicConstraintVersion() {
+        return delegate.getDynamicConstraintVersion();
     }
 
     public boolean isChanging() {
@@ -53,6 +71,10 @@ public class DslOriginDependencyMetaDataWrapper implements DslOriginDependencyMe
         return delegate.isTransitive();
     }
 
+    public boolean isForce() {
+        return delegate.isForce();
+    }
+
     public Set<ComponentArtifactMetaData> getArtifacts(ConfigurationMetaData fromConfiguration, ConfigurationMetaData toConfiguration) {
         return delegate.getArtifacts(fromConfiguration, toConfiguration);
     }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalArtifactMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalArtifactMetaData.java
deleted file mode 100644
index 601e49a..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalArtifactMetaData.java
+++ /dev/null
@@ -1,25 +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.internal.component.local.model;
-
-import org.gradle.internal.component.model.ComponentArtifactMetaData;
-
-import java.io.File;
-
-public interface LocalArtifactMetaData extends ComponentArtifactMetaData {
-    File getFile();
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentArtifactIdentifier.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentArtifactIdentifier.java
new file mode 100644
index 0000000..d43c72c
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentArtifactIdentifier.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.local.model;
+
+import org.gradle.internal.component.model.ComponentArtifactIdentifier;
+
+import java.io.File;
+
+public interface LocalComponentArtifactIdentifier extends ComponentArtifactIdentifier {
+    File getFile();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentMetaData.java
index d885549..7a4689e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalComponentMetaData.java
@@ -16,10 +16,8 @@
 
 package org.gradle.internal.component.local.model;
 
-import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.internal.component.external.model.BuildableIvyModulePublishMetaData;
-import org.gradle.internal.component.model.ComponentArtifactIdentifier;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
 
 public interface LocalComponentMetaData {
@@ -35,6 +33,4 @@ public interface LocalComponentMetaData {
      */
     BuildableIvyModulePublishMetaData toPublishMetaData();
 
-    @Nullable
-    LocalArtifactMetaData getArtifact(ComponentArtifactIdentifier artifactIdentifier);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalConfigurationMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalConfigurationMetaData.java
new file mode 100644
index 0000000..0ce0b0d
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/LocalConfigurationMetaData.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.local.model;
+
+import org.gradle.internal.component.model.ConfigurationMetaData;
+
+import java.util.Set;
+
+public interface LocalConfigurationMetaData extends ConfigurationMetaData {
+
+    String getDescription();
+
+    Set<String> getExtendsFrom();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MissingLocalArtifactMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MissingLocalArtifactMetaData.java
new file mode 100644
index 0000000..a1c13ad
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MissingLocalArtifactMetaData.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.internal.component.local.model;
+
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.IvyArtifactName;
+
+import java.io.File;
+
+public class MissingLocalArtifactMetaData implements LocalComponentArtifactIdentifier, ComponentArtifactMetaData {
+    private final ComponentIdentifier componentIdentifier;
+    private final String componentDisplayName;
+    private final IvyArtifactName name;
+
+    public MissingLocalArtifactMetaData(ComponentIdentifier componentIdentifier, String componentDisplayName, IvyArtifactName artifactName) {
+        this.componentIdentifier = componentIdentifier;
+        this.componentDisplayName = componentDisplayName;
+        this.name = artifactName;
+    }
+
+    public String getDisplayName() {
+        return String.format("%s (%s)", name, componentDisplayName);
+    }
+
+    @Override
+    public File getFile() {
+        return null;
+    }
+
+    public IvyArtifactName getName() {
+        return name;
+    }
+
+    public ComponentIdentifier getComponentIdentifier() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public LocalComponentArtifactIdentifier getId() {
+        return this;
+    }
+
+    @Override
+    public ComponentIdentifier getComponentId() {
+        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;
+        }
+        MissingLocalArtifactMetaData other = (MissingLocalArtifactMetaData) obj;
+        return other.componentIdentifier.equals(componentIdentifier) && other.name.equals(name);
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MutableLocalComponentMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MutableLocalComponentMetaData.java
index 230d92a..0daeece 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MutableLocalComponentMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/MutableLocalComponentMetaData.java
@@ -17,18 +17,15 @@
 package org.gradle.internal.component.local.model;
 
 import org.apache.ivy.core.module.descriptor.ExcludeRule;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.component.model.IvyArtifactName;
 
-import java.io.File;
+import java.util.Set;
 
 public interface MutableLocalComponentMetaData extends LocalComponentMetaData {
-    ModuleDescriptor getModuleDescriptor();
+    void addArtifacts(String configuration, Iterable<? extends PublishArtifact> artifacts);
 
-    void addArtifact(String configuration, IvyArtifactName artifact, File file);
-
-    void addConfiguration(String name, boolean visible, String description, String[] superConfigs, boolean transitive);
+    void addConfiguration(String name, String description, Set<String> extendsFrom, Set<String> hierarchy, boolean visible, boolean transitive);
 
     void addDependency(DependencyMetaData dependency);
 
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/PublishArtifactLocalArtifactMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/PublishArtifactLocalArtifactMetaData.java
new file mode 100644
index 0000000..c54e772
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/PublishArtifactLocalArtifactMetaData.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.local.model;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.internal.component.model.ComponentArtifactMetaData;
+import org.gradle.internal.component.model.DefaultIvyArtifactName;
+import org.gradle.internal.component.model.IvyArtifactName;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+public class PublishArtifactLocalArtifactMetaData implements LocalComponentArtifactIdentifier, ComponentArtifactMetaData {
+    private final ComponentIdentifier componentIdentifier;
+    private final String moduleName;
+    private final String componentDisplayName;
+    private final PublishArtifact publishArtifact;
+
+    // The componentDisplayName parameter is temporary
+    public PublishArtifactLocalArtifactMetaData(ComponentIdentifier componentIdentifier, String moduleName, String componentDisplayName, PublishArtifact publishArtifact) {
+        this.componentIdentifier = componentIdentifier;
+        this.moduleName = moduleName;
+        this.componentDisplayName = componentDisplayName;
+        this.publishArtifact = publishArtifact;
+    }
+
+    public String getDisplayName() {
+        StringBuilder result = new StringBuilder();
+        result.append(publishArtifact.getName());
+        String classifier = publishArtifact.getClassifier();
+        if (GUtil.isTrue(classifier)) {
+            result.append("-");
+            result.append(classifier);
+        }
+        String extension = publishArtifact.getExtension();
+        if (GUtil.isTrue(extension)) {
+            result.append(".");
+            result.append(extension);
+        }
+        result.append(" (")
+              .append(componentDisplayName)
+              .append(")");
+        return result.toString();
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public ComponentIdentifier getComponentIdentifier() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public File getFile() {
+        return publishArtifact.getFile();
+    }
+
+    @Override
+    public LocalComponentArtifactIdentifier getId() {
+        return this;
+    }
+
+    @Override
+    public ComponentIdentifier getComponentId() {
+        return componentIdentifier;
+    }
+
+    @Override
+    public IvyArtifactName getName() {
+        return DefaultIvyArtifactName.forPublishArtifact(publishArtifact, moduleName);
+    }
+
+    @Override
+    public int hashCode() {
+        return componentIdentifier.hashCode() ^ publishArtifact.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        PublishArtifactLocalArtifactMetaData other = (PublishArtifactLocalArtifactMetaData) obj;
+        return other.componentIdentifier.equals(componentIdentifier) && other.publishArtifact.equals(publishArtifact);
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AbstractModuleDescriptorBackedMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AbstractModuleDescriptorBackedMetaData.java
index 045ff4a..88d1524 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AbstractModuleDescriptorBackedMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AbstractModuleDescriptorBackedMetaData.java
@@ -16,18 +16,20 @@
 
 package org.gradle.internal.component.model;
 
+import com.google.common.collect.Sets;
 import org.apache.ivy.core.module.descriptor.Configuration;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ExcludeRule;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.internal.component.external.model.DefaultModuleComponentArtifactMetaData;
+import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData;
 import org.gradle.util.CollectionUtils;
 
 import java.util.*;
 
 public abstract class AbstractModuleDescriptorBackedMetaData implements ComponentResolveMetaData {
-    private static final List<String> DEFAULT_STATUS_SCHEME = Arrays.asList("integration", "milestone", "release");
 
     private ModuleVersionIdentifier moduleVersionIdentifier;
     private final ModuleDescriptor moduleDescriptor;
@@ -141,6 +143,11 @@ public abstract class AbstractModuleDescriptorBackedMetaData implements Componen
         }
     }
 
+    @Override
+    public Set<String> getConfigurationNames() {
+        return Sets.newHashSet(moduleDescriptor.getConfigurationsNames());
+    }
+
     public DefaultConfigurationMetaData getConfiguration(final String name) {
         DefaultConfigurationMetaData configuration = configurations.get(name);
         if (configuration == null) {
@@ -201,6 +208,10 @@ public abstract class AbstractModuleDescriptorBackedMetaData implements Componen
             return descriptor.isTransitive();
         }
 
+        public boolean isVisible() {
+            return descriptor.getVisibility() == Configuration.Visibility.PUBLIC;
+        }
+
         public List<DependencyMetaData> getDependencies() {
             if (dependencies == null) {
                 dependencies = new ArrayList<DependencyMetaData>();
@@ -214,7 +225,7 @@ public abstract class AbstractModuleDescriptorBackedMetaData implements Componen
         }
 
         private boolean include(DependencyMetaData dependency) {
-            String[] moduleConfigurations = dependency.getDescriptor().getModuleConfigurations();
+            String[] moduleConfigurations = dependency.getModuleConfigurations();
             for (int i = 0; i < moduleConfigurations.length; i++) {
                 String moduleConfiguration = moduleConfigurations[i];
                 if (moduleConfiguration.equals("%") || hierarchy.contains(moduleConfiguration)) {
@@ -261,5 +272,9 @@ public abstract class AbstractModuleDescriptorBackedMetaData implements Componen
             }
             return artifacts;
         }
+
+        public ModuleComponentArtifactMetaData artifact(IvyArtifactName artifact) {
+            return new DefaultModuleComponentArtifactMetaData((org.gradle.api.artifacts.component.ModuleComponentIdentifier) getComponentId(), artifact);
+        }
     }
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentOverrideMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentOverrideMetadata.java
new file mode 100644
index 0000000..eb7b763
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentOverrideMetadata.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.model;
+
+import org.gradle.api.artifacts.ClientModule;
+
+import java.util.Set;
+
+/**
+ * Metadata about a component that will override the information obtained when resolving, typically specified by a dependency descriptor.
+ * Metadata supplied in this way is applied inconsistently, because multiple dependencies can point to the same component with different
+ * override metadata, and only one of these overrides will be used during dependency resolution.
+ */
+public interface ComponentOverrideMetadata {
+
+    Set<IvyArtifactName> getArtifacts();
+
+    /**
+     * If the request originated from a ClientModule, return it. Null otherwise.
+     */
+    ClientModule getClientModule();
+
+    boolean isChanging();
+
+    ComponentOverrideMetadata withChanging();
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentResolveMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentResolveMetaData.java
index e0bd6b6..72d5e90 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentResolveMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ComponentResolveMetaData.java
@@ -16,12 +16,11 @@
 
 package org.gradle.internal.component.model;
 
-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 java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
@@ -29,6 +28,8 @@ import java.util.Set;
  * The meta-data for a component instance that is required during dependency resolution.
  */
 public interface ComponentResolveMetaData {
+    List<String> DEFAULT_STATUS_SCHEME = Arrays.asList("integration", "milestone", "release");
+
     /**
      * Returns the identifier for this component.
      */
@@ -53,15 +54,13 @@ public interface ComponentResolveMetaData {
      */
     ComponentResolveMetaData withSource(ModuleSource source);
 
+    List<DependencyMetaData> getDependencies();
+
     /**
-     * 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.
-     *
-     * <p>You should avoid using this method.
+     * Returns the names of all of the configurations for this component.
      */
-    ModuleDescriptor getDescriptor();
-
-    List<DependencyMetaData> getDependencies();
+    // TODO:DAZ Maybe getConfigurations() would be better?
+    Set<String> getConfigurationNames();
 
     /**
      * Locates the configuration with the given name, if any.
@@ -69,17 +68,6 @@ public interface ComponentResolveMetaData {
     @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 isGenerated();
 
     boolean isChanging();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ConfigurationMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ConfigurationMetaData.java
index 22aac71..e7b8adf 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ConfigurationMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ConfigurationMetaData.java
@@ -38,4 +38,15 @@ public interface ConfigurationMetaData {
     Set<ExcludeRule> getExcludeRules();
 
     boolean isTransitive();
+
+    boolean isVisible();
+
+    /**
+     * Find the component artifact with the given IvyArtifactName, creating a new one if none matches.
+     *
+     * This is used to create a ComponentArtifactMetaData from an artifact declared as part of a dependency.
+     * The reason to do this lookup is that for a local component artifact, the file is part of the artifact metadata.
+     * (For external module components, we just instantiate a new artifact metadata).
+     */
+    ComponentArtifactMetaData artifact(IvyArtifactName artifact);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultComponentOverrideMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultComponentOverrideMetadata.java
new file mode 100644
index 0000000..7cc88b6
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultComponentOverrideMetadata.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.model;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class DefaultComponentOverrideMetadata implements ComponentOverrideMetadata {
+    private final boolean changing;
+    private final Set<IvyArtifactName> artifacts;
+    private final ClientModule clientModule;
+
+    public static ComponentOverrideMetadata forDependency(DependencyMetaData dependencyMetaData) {
+        return new DefaultComponentOverrideMetadata(dependencyMetaData.isChanging(), dependencyMetaData.getArtifacts(), extractClientModule(dependencyMetaData));
+    }
+
+    public DefaultComponentOverrideMetadata() {
+        this(false, Collections.<IvyArtifactName>emptySet(), null);
+    }
+
+    private DefaultComponentOverrideMetadata(boolean changing, Set<IvyArtifactName> artifacts, ClientModule clientModule) {
+        this.changing = changing;
+        this.artifacts = Sets.newHashSet(artifacts);
+        this.clientModule = clientModule;
+    }
+
+    private static ClientModule extractClientModule(DependencyMetaData dependencyMetaData) {
+        if (dependencyMetaData instanceof DslOriginDependencyMetaData) {
+            ModuleDependency source = ((DslOriginDependencyMetaData) dependencyMetaData).getSource();
+            if (source instanceof ClientModule) {
+                return (ClientModule) source;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ComponentOverrideMetadata withChanging() {
+        return new DefaultComponentOverrideMetadata(true, artifacts, clientModule);
+    }
+
+    @Override
+    public Set<IvyArtifactName> getArtifacts() {
+        return artifacts;
+    }
+
+    @Override
+    public boolean isChanging() {
+        return changing;
+    }
+
+    @Override
+    public ClientModule getClientModule() {
+        return clientModule;
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultDependencyMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultDependencyMetaData.java
index 3a8624c..db9443c 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultDependencyMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultDependencyMetaData.java
@@ -33,9 +33,7 @@ import org.gradle.internal.component.external.model.DefaultModuleComponentSelect
 import org.gradle.internal.component.local.model.DefaultProjectDependencyMetaData;
 
 import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.*;
 
 public class DefaultDependencyMetaData implements DependencyMetaData {
     private final DependencyDescriptor dependencyDescriptor;
@@ -66,6 +64,20 @@ public class DefaultDependencyMetaData implements DependencyMetaData {
         return requested;
     }
 
+    @Override
+    public String[] getModuleConfigurations() {
+        return dependencyDescriptor.getModuleConfigurations();
+    }
+
+    @Override
+    public String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration) {
+        return dependencyDescriptor.getDependencyConfigurations(moduleConfiguration, requestedConfiguration);
+    }
+
+    public ExcludeRule[] getExcludeRules(Collection<String> configurations) {
+        return dependencyDescriptor.getExcludeRules(configurations.toArray(new String[configurations.size()]));
+    }
+
     public boolean isChanging() {
         return dependencyDescriptor.isChanging();
     }
@@ -74,6 +86,14 @@ public class DefaultDependencyMetaData implements DependencyMetaData {
         return dependencyDescriptor.isTransitive();
     }
 
+    public boolean isForce() {
+        return dependencyDescriptor.isForce();
+    }
+
+    public String getDynamicConstraintVersion() {
+        return dependencyDescriptor.getDynamicConstraintDependencyRevisionId().getRevision();
+    }
+
     public DependencyDescriptor getDescriptor() {
         return dependencyDescriptor;
     }
@@ -86,9 +106,8 @@ public class DefaultDependencyMetaData implements DependencyMetaData {
         }
         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));
+            DefaultIvyArtifactName artifact = DefaultIvyArtifactName.forIvyArtifact(artifactDescriptor);
+            artifacts.add(toConfiguration.artifact(artifact));
         }
         return artifacts;
     }
@@ -99,8 +118,9 @@ public class DefaultDependencyMetaData implements DependencyMetaData {
             return Collections.emptySet();
         }
         Set<IvyArtifactName> artifactSet = Sets.newLinkedHashSet();
-        for (DependencyArtifactDescriptor artifact : dependencyArtifacts) {
-            artifactSet.add(new DefaultIvyArtifactName(artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttributes()));
+        for (DependencyArtifactDescriptor artifactDescriptor : dependencyArtifacts) {
+            DefaultIvyArtifactName artifact = DefaultIvyArtifactName.forIvyArtifact(artifactDescriptor);
+            artifactSet.add(artifact);
         }
         return artifactSet;
     }
@@ -126,7 +146,7 @@ public class DefaultDependencyMetaData implements DependencyMetaData {
         } else if (target instanceof ProjectComponentSelector) {
             // TODO:Prezi what to do here?
             ProjectComponentSelector projectTarget = (ProjectComponentSelector) target;
-            return new DefaultProjectDependencyMetaData(getDescriptor(), projectTarget.getProjectPath());
+            return new DefaultProjectDependencyMetaData(dependencyDescriptor, projectTarget.getProjectPath());
         } else {
             throw new AssertionError();
         }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultIvyArtifactName.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultIvyArtifactName.java
index 8a21c12..0734df7 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultIvyArtifactName.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DefaultIvyArtifactName.java
@@ -18,7 +18,9 @@ package org.gradle.internal.component.model;
 
 import com.google.common.base.Objects;
 import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
 import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.util.GUtil;
 
 import java.util.Collections;
@@ -32,8 +34,18 @@ public class DefaultIvyArtifactName implements IvyArtifactName {
     private final String extension;
     private final Map<String, String> attributes;
 
-    public DefaultIvyArtifactName(Artifact a) {
-        this(a.getName(), a.getType(), a.getExt(), a.getAttributes());
+    public static DefaultIvyArtifactName forIvyArtifact(Artifact a) {
+        return new DefaultIvyArtifactName(a.getName(), a.getType(), a.getExt(), a.getExtraAttributes());
+    }
+
+    public static DefaultIvyArtifactName forIvyArtifact(DependencyArtifactDescriptor a) {
+        return new DefaultIvyArtifactName(a.getName(), a.getType(), a.getExt(), a.getExtraAttributes());
+    }
+
+    public static DefaultIvyArtifactName forPublishArtifact(PublishArtifact publishArtifact, String moduleName) {
+        String name = GUtil.elvis(publishArtifact.getName(), moduleName);
+        String classifier = GUtil.elvis(publishArtifact.getClassifier(), null);
+        return new DefaultIvyArtifactName(name, publishArtifact.getType(), publishArtifact.getExtension(), classifier);
     }
 
     public DefaultIvyArtifactName(String name, String type, @Nullable String extension, Map<String, String> attributes) {
@@ -59,6 +71,17 @@ public class DefaultIvyArtifactName implements IvyArtifactName {
         this.attributes = Collections.emptyMap();
     }
 
+    public DefaultIvyArtifactName(String name, String type, @Nullable String extension, @Nullable String classifier) {
+        this.name = name;
+        this.type = type;
+        this.extension = extension;
+        if (classifier == null) {
+            this.attributes = Collections.emptyMap();
+        } else {
+            this.attributes = Collections.singletonMap(CLASSIFIER, classifier);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder result = new StringBuilder();
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DependencyMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DependencyMetaData.java
index 5eaea6f..b6b8139 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DependencyMetaData.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/DependencyMetaData.java
@@ -16,28 +16,17 @@
 
 package org.gradle.internal.component.model;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.component.ComponentSelector;
 
+import java.util.Collection;
 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.
-     *
-     * <p>You should avoid using this method.
-     */
-    DependencyDescriptor getDescriptor();
-
-    boolean isChanging();
-
-    boolean isTransitive();
-
-    /**
      * Returns the artifacts referenced by this dependency for the given combination of source and target configurations, if any. Returns an empty set if
      * this dependency does not reference any specific artifacts - the defaults for the target configuration should be used in this case.
      */
@@ -71,4 +60,20 @@ public interface DependencyMetaData {
      * @return Component selector
      */
     ComponentSelector getSelector();
+
+    // The following methods all wrap an underlying method on DependencyDescriptor that we use, to help migrate away from using Ivy types.
+    String[] getModuleConfigurations();
+
+    String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration);
+
+    ExcludeRule[] getExcludeRules(Collection<String> configurations);
+
+    boolean isChanging();
+
+    boolean isTransitive();
+
+    boolean isForce();
+
+    String getDynamicConstraintVersion();
+
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetaData.java
new file mode 100644
index 0000000..8337560
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetaData.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.model;
+
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+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.component.ProjectComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class LocalComponentDependencyMetaData implements DependencyMetaData {
+    private final ComponentSelector selector;
+    private final ModuleVersionSelector requested;
+    private final String moduleConfiguration;
+    private final String dependencyConfiguration;
+    private final ExcludeRule[] excludeRules;
+    private final Set<IvyArtifactName> artifactNames;
+    private final boolean force;
+    private final boolean changing;
+    private final boolean transitive;
+
+    public LocalComponentDependencyMetaData(ComponentSelector selector, ModuleVersionSelector requested, String moduleConfiguration, String dependencyConfiguration,
+                                            Set<IvyArtifactName> artifactNames, ExcludeRule[] excludeRules,
+                                            boolean force, boolean changing, boolean transitive) {
+        this.selector = selector;
+        this.requested = requested;
+        this.moduleConfiguration = moduleConfiguration;
+        this.dependencyConfiguration = dependencyConfiguration;
+        this.artifactNames = artifactNames;
+        this.excludeRules = excludeRules;
+        this.force = force;
+        this.changing = changing;
+        this.transitive = transitive;
+    }
+
+    @Override
+    public String toString() {
+        // TODO:DAZ Improve this copy from Ivy
+        return "dependency: " + requested + " " + moduleConfiguration;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public ComponentSelector getSelector() {
+        return selector;
+    }
+
+    public String[] getModuleConfigurations() {
+        return new String[] {
+            moduleConfiguration
+        };
+    }
+
+    public String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration) {
+        if (this.moduleConfiguration.equals(moduleConfiguration)) {
+            return new String[] {
+                dependencyConfiguration
+            };
+        }
+        return new String[0];
+    }
+
+    public ExcludeRule[] getExcludeRules(Collection<String> configurations) {
+        if (configurations.contains(moduleConfiguration)) {
+            return excludeRules;
+        }
+        return new ExcludeRule[0];
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public String getDynamicConstraintVersion() {
+        return requested.getVersion();
+    }
+
+    public Set<ComponentArtifactMetaData> getArtifacts(ConfigurationMetaData fromConfiguration, ConfigurationMetaData toConfiguration) {
+        if (artifactNames.isEmpty()) {
+            return Collections.emptySet();
+        }
+        Set<ComponentArtifactMetaData> artifacts = new LinkedHashSet<ComponentArtifactMetaData>();
+        for (IvyArtifactName artifactName : artifactNames) {
+            artifacts.add(toConfiguration.artifact(artifactName));
+        }
+        return artifacts;
+    }
+
+    public Set<IvyArtifactName> getArtifacts() {
+        return artifactNames;
+    }
+
+    public DependencyMetaData withRequestedVersion(String requestedVersion) {
+        if (requestedVersion.equals(requested.getVersion())) {
+            return this;
+        }
+        ModuleVersionSelector newRequested = DefaultModuleVersionSelector.newSelector(requested.getGroup(), requested.getName(), requestedVersion);
+        ComponentSelector newSelector = DefaultModuleComponentSelector.newSelector(newRequested);
+        return copyWithTarget(newSelector, newRequested);
+    }
+
+    @Override
+    public DependencyMetaData withTarget(ComponentSelector target) {
+        if (target instanceof ModuleComponentSelector) {
+            ModuleComponentSelector moduleTarget = (ModuleComponentSelector) target;
+            ModuleVersionSelector requestedVersion = DefaultModuleVersionSelector.newSelector(moduleTarget.getGroup(), moduleTarget.getModule(), moduleTarget.getVersion());
+            return copyWithTarget(moduleTarget, requestedVersion);
+        } else if (target instanceof ProjectComponentSelector) {
+            return copyWithTarget(target, requested);
+        } else {
+            throw new AssertionError("Invalid component selector type for substitution: " + target);
+        }
+    }
+
+    private DependencyMetaData copyWithTarget(ComponentSelector selector, ModuleVersionSelector requested) {
+        return new LocalComponentDependencyMetaData(selector, requested, moduleConfiguration, dependencyConfiguration, artifactNames, excludeRules, force, changing, transitive);
+    }
+
+    public DependencyMetaData withChanging() {
+        if (isChanging()) {
+            return this;
+        }
+        return new LocalComponentDependencyMetaData(selector, requested, moduleConfiguration, dependencyConfiguration, artifactNames, excludeRules, force, true, transitive);
+    }
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ModuleComponentArtifactsMetaData.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ModuleComponentArtifactsMetaData.java
new file mode 100644
index 0000000..ffc0622
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/ModuleComponentArtifactsMetaData.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.component.model;
+
+public interface ModuleComponentArtifactsMetaData {
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ComponentMetaDataResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ComponentMetaDataResolver.java
index 4429908..11de70e 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ComponentMetaDataResolver.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ComponentMetaDataResolver.java
@@ -17,12 +17,12 @@
 package org.gradle.internal.resolve.resolver;
 
 import org.gradle.api.artifacts.component.ComponentIdentifier;
-import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
 
 public interface ComponentMetaDataResolver {
     /**
      * Resolves the meta-data for a component instance. Failures should be attached to the returned result.
      */
-    void resolve(DependencyMetaData dependency, ComponentIdentifier identifier, BuildableComponentResolveResult result);
+    void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result);
 }
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/DependencyToComponentResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/DependencyToComponentResolver.java
deleted file mode 100755
index 52726e0..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/DependencyToComponentResolver.java
+++ /dev/null
@@ -1,29 +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.internal.resolve.resolver;
-
-import org.gradle.internal.component.model.DependencyMetaData;
-import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
-
-/**
- * Resolves a dependency to a component instance.
- */
-public interface DependencyToComponentResolver {
-    /**
-     * 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/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ModuleToComponentResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ModuleToComponentResolver.java
deleted file mode 100755
index 0f938d9..0000000
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ModuleToComponentResolver.java
+++ /dev/null
@@ -1,30 +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.internal.resolve.resolver;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.artifacts.ModuleInternal;
-import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
-
-import java.util.Set;
-
-/**
- * Resolves a module to the meta-data for a module.
- */
-public interface ModuleToComponentResolver {
-    void resolve(ModuleInternal module, Set<? extends Configuration> configurations, BuildableComponentResolveResult result);
-}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ResolveContextToComponentResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ResolveContextToComponentResolver.java
new file mode 100755
index 0000000..138fc8f
--- /dev/null
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/resolver/ResolveContextToComponentResolver.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.internal.resolve.resolver;
+
+import org.gradle.api.artifacts.ResolveContext;
+import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
+
+/**
+ * Resolves a context to the meta-data for the provided {@link ResolveContext}.
+ */
+public interface ResolveContextToComponentResolver {
+    void resolve(ResolveContext resolveContext, BuildableComponentResolveResult result);
+}
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/BuildableComponentResolveResult.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/BuildableComponentResolveResult.java
index 7c7b194..1a86477 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/BuildableComponentResolveResult.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/BuildableComponentResolveResult.java
@@ -16,7 +16,7 @@
 
 package org.gradle.internal.resolve.result;
 
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
 import org.gradle.internal.resolve.ModuleVersionResolveException;
 
@@ -34,7 +34,7 @@ public interface BuildableComponentResolveResult extends ComponentResolveResult,
     /**
      * Marks the component as not found.
      */
-    void notFound(ModuleVersionIdentifier versionIdentifier);
+    void notFound(ModuleComponentIdentifier versionIdentifier);
 
     /**
      * Replaces the meta-data in the result. Result must already be resolved.
diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResult.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResult.java
index 0b77d4e..ae52b2f 100644
--- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResult.java
+++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResult.java
@@ -17,6 +17,8 @@
 package org.gradle.internal.resolve.result;
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.internal.component.model.ComponentResolveMetaData;
 import org.gradle.internal.resolve.ModuleVersionNotFoundException;
 import org.gradle.internal.resolve.ModuleVersionResolveException;
@@ -31,8 +33,9 @@ public class DefaultBuildableComponentResolveResult extends DefaultResourceAware
         return this;
     }
 
-    public void notFound(ModuleVersionIdentifier versionIdentifier) {
-        failed(new ModuleVersionNotFoundException(versionIdentifier, getAttempted()));
+    @Override
+    public void notFound(ModuleComponentIdentifier versionIdentifier) {
+        failed(new ModuleVersionNotFoundException(DefaultModuleVersionIdentifier.newId(versionIdentifier), getAttempted()));
     }
 
     public void resolved(ComponentResolveMetaData metaData) {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
index 508e2fc..3f668ea 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
@@ -29,10 +29,10 @@ class DefaultResolvedArtifactTest extends Specification {
         def dependencySameModule = dep("group", "module1", "1.2")
         def dependency2 = dep("group", "module2", "1-beta")
         def ivyArt = Stub(IvyArtifactName)
-        def artifact = new DefaultResolvedArtifact(dependency, ivyArt, artifactSource, 0)
-        def equalArtifact = new DefaultResolvedArtifact(dependencySameModule, ivyArt, artifactSource, 0)
-        def differentModule = new DefaultResolvedArtifact(dependency2, ivyArt, artifactSource, 0)
-        def differentName = new DefaultResolvedArtifact(dependency, Stub(IvyArtifactName), artifactSource, 0)
+        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, Stub(IvyArtifactName), artifactSource)
 
         expect:
         artifact Matchers.strictlyEqual(equalArtifact)
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
index 4f1603c..439f3e1 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
@@ -115,7 +115,7 @@ public class DefaultResolvedDependencyTest {
             allowing(version).getId();
             will(returnValue(new DefaultModuleVersionIdentifier("group", name, "1.2")));
         }});
-        return new DefaultResolvedArtifact(resolvedDependency.getModule(), artifactStub, artifactSource, 0);
+        return new DefaultResolvedArtifact(resolvedDependency.getModule(), artifactStub, artifactSource);
     }
 
     private DefaultResolvedDependency createResolvedDependency() {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
index 3d2d62f..acaa092 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
@@ -16,9 +16,11 @@
 
 package org.gradle.api.internal.artifacts
 
+import org.gradle.api.Action
 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.ivyservice.resolveengine.projectresult.ResolvedProjectConfiguration
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResults
 import spock.lang.Specification
 
@@ -31,13 +33,11 @@ class ResolverResultsSpec extends Specification {
 
     def "does not provide ResolutionResult in case of fatal failure"() {
         when:
-        results.failed(resolvedConfiguration, fatalFailure)
+        results.failed(fatalFailure)
 
-        then:
-        results.resolvedConfiguration
-
-        when:
+        and:
         results.resolutionResult
+
         then:
         def ex = thrown(ResolveException)
         ex == fatalFailure
@@ -45,11 +45,25 @@ class ResolverResultsSpec extends Specification {
 
     def "provides resolve results"() {
         when:
-        results.resolved(resolvedConfiguration, resolutionResult, projectConfigurationResult)
+        results.resolved(resolutionResult, projectConfigurationResult)
+        results.withResolvedConfiguration(resolvedConfiguration)
 
         then:
         results.resolvedConfiguration == resolvedConfiguration
         results.resolutionResult == resolutionResult
-        results.resolvedProjectConfigurationResults == projectConfigurationResult
+    }
+
+    def "performs action for each resolved project configuration"() {
+        Action<ResolvedProjectConfiguration> action = Mock(Action)
+        def projectConfiguration = Mock(ResolvedProjectConfiguration)
+
+        projectConfigurationResult.get() >> [projectConfiguration]
+
+        when:
+        results.resolved(resolutionResult, projectConfigurationResult)
+        results.eachResolvedProject(action)
+
+        then:
+        1 * action.execute(projectConfiguration)
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
index b6a7920..c7c0dd0 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -19,6 +19,7 @@ import org.gradle.api.artifacts.UnknownConfigurationException
 import org.gradle.api.internal.DomainObjectContext
 import org.gradle.api.internal.artifacts.ConfigurationResolver
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy
 import org.gradle.initialization.ProjectAccessListener
 import org.gradle.internal.reflect.Instantiator
@@ -33,19 +34,20 @@ public class DefaultConfigurationContainerSpec extends Specification {
     private ListenerManager listenerManager = Mock()
     private DependencyMetaDataProvider metaDataProvider = Mock()
     private ProjectAccessListener projectAccessListener = Mock()
+    private ProjectFinder projectFinder = Mock()
 
     def ConfigurationInternal conf = Mock()
 
     private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(
             resolver, instantiator, domainObjectContext,
-            listenerManager, metaDataProvider, projectAccessListener);
+            listenerManager, metaDataProvider, projectAccessListener, projectFinder);
 
     def "adds and gets"() {
         _ * conf.getName() >> "compile"
         1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
         1 * instantiator.newInstance(DefaultResolutionStrategy.class) >> { new DefaultResolutionStrategy() }
         1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
-                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal, projectAccessListener) >> conf
+                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal, projectAccessListener, projectFinder) >> conf
 
         when:
         def compile = configurationContainer.create("compile")
@@ -65,7 +67,7 @@ public class DefaultConfigurationContainerSpec extends Specification {
         1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
         1 * instantiator.newInstance(DefaultResolutionStrategy.class) >> { new DefaultResolutionStrategy() }
         1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
-                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal, projectAccessListener) >> conf
+                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal, projectAccessListener, projectFinder) >> conf
 
         when:
         def compile = configurationContainer.create("compile") {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
index 07f7bba..a114553 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -23,10 +23,11 @@ import org.gradle.api.internal.ClassGeneratorBackedInstantiator
 import org.gradle.api.internal.DomainObjectContext
 import org.gradle.api.internal.MissingMethodException
 import org.gradle.api.internal.artifacts.ConfigurationResolver
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
 import org.gradle.initialization.ProjectAccessListener
+import org.gradle.internal.event.ListenerManager
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
-import org.gradle.internal.event.ListenerManager
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
@@ -36,7 +37,6 @@ import org.junit.runner.RunWith
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
-
 @RunWith(JMock)
 class DefaultConfigurationContainerTest {
     private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
@@ -48,7 +48,7 @@ class DefaultConfigurationContainerTest {
     private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), DirectInstantiator.INSTANCE)
     private DefaultConfigurationContainer configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
             resolver, instantiator, { name -> name } as DomainObjectContext,
-            listenerManager, metaDataProvider, projectAccessListener)
+            listenerManager, metaDataProvider, projectAccessListener, context.mock(ProjectFinder))
 
     @Before
     public void setup() {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
index 39feb9e..60dcd50 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
@@ -17,58 +17,527 @@ package org.gradle.api.internal.artifacts.configurations
 
 import org.gradle.api.Action
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.*
 import org.gradle.api.artifacts.result.ResolutionResult
 import org.gradle.api.internal.artifacts.ConfigurationResolver
+import org.gradle.api.internal.artifacts.DefaultExcludeRule
+import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet
 import org.gradle.api.internal.artifacts.ResolverResults
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResults
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.specs.Spec
 import org.gradle.api.tasks.TaskDependency
+import org.gradle.initialization.ProjectAccessListener
 import org.gradle.internal.event.ListenerBroadcast
 import org.gradle.internal.event.ListenerManager
+import org.gradle.util.WrapUtil
 import spock.lang.Specification
 
+import static org.gradle.api.artifacts.Configuration.State.*
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+
 class DefaultConfigurationSpec extends Specification {
 
-    ConfigurationsProvider configurationsProvider = Mock()
-    ConfigurationResolver resolver = Mock()
-    ListenerManager listenerManager = Mock()
-    DependencyMetaDataProvider metaDataProvider = Mock()
-    ResolutionStrategyInternal resolutionStrategy = Mock()
+    def configurationsProvider = Mock(ConfigurationsProvider)
+    def resolver = Mock(ConfigurationResolver)
+    def listenerManager = Mock(ListenerManager)
+    def metaDataProvider = Mock(DependencyMetaDataProvider)
+    def resolutionStrategy = Mock(ResolutionStrategyInternal)
+    def projectAccessListener = Mock(ProjectAccessListener)
+    def projectFinder = Mock(ProjectFinder)
 
-    DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
-        new DefaultConfiguration(path, confName, configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy, null)
+    def setup() {
+        ListenerBroadcast<DependencyResolutionListener> broadcast = new ListenerBroadcast<DependencyResolutionListener>(DependencyResolutionListener)
+        _ * listenerManager.createAnonymousBroadcaster(DependencyResolutionListener) >> broadcast
     }
 
-    DefaultPublishArtifact artifact(String name) {
-        artifact(name: name)
+    def void defaultValues() {
+        when:
+        def configuration = conf("name", "path")
+
+        then:
+        configuration.name == "name"
+        configuration.visible
+        configuration.extendsFrom.empty
+        configuration.transitive
+        configuration.description == null
+        configuration.state == UNRESOLVED
+        configuration.displayName == "configuration 'path'"
+        configuration.uploadTaskName == "uploadName"
     }
 
-    DefaultPublishArtifact artifact(Map props = [:]) {
-        new DefaultPublishArtifact(
-            props.name ?: "artifact",
-            props.extension ?: "artifact",
-            props.type,
-            props.classifier,
-            props.date,
-            props.file,
-            props.tasks ?: []
-        )
+    def hasUsefulDisplayName() {
+        when:
+        def configuration = conf("name", "path")
+
+        then:
+        configuration.displayName == "configuration 'path'"
+        configuration.toString() == "configuration 'path'"
+        configuration.incoming.toString() == "dependencies 'path'"
     }
 
-    // You need to wrap this in an interaction {} block when calling it
-    ResolvedConfiguration resolvedConfiguration(Configuration config, ConfigurationResolver dependencyResolver = resolver) {
-        ResolvedConfiguration resolvedConfiguration = Mock()
-        def results = new ResolverResults()
-        results.resolved(resolvedConfiguration, Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults))
-        1 * dependencyResolver.resolve(config) >> results
-        resolvedConfiguration
+    def "set description, visibility and transitivity"() {
+        given:
+        def configuration = conf()
+
+        when:
+        configuration.setDescription("description")
+        configuration.setVisible(false)
+        configuration.setTransitive(false)
+
+        then:
+        configuration.description == "description"
+        !configuration.visible
+        !configuration.transitive
     }
 
-    def setup() {
-        ListenerBroadcast<DependencyResolutionListener> broadcast = new ListenerBroadcast<DependencyResolutionListener>(DependencyResolutionListener)
-        _ * listenerManager.createAnonymousBroadcaster(DependencyResolutionListener) >> broadcast
+    def excludes() {
+        def excludeArgs1 = [group: "aGroup"]
+        def excludeArgs2 = [module: "aModule"]
+        def configuration = conf()
+        def rule = new DefaultExcludeRule("groupValue", null)
+
+        when:
+        configuration.exclude(excludeArgs1)
+        configuration.exclude(excludeArgs2);
+
+        then:
+        configuration.excludeRules == [new DefaultExcludeRule("aGroup", null), new DefaultExcludeRule(null, "aModule")] as Set
+
+        when:
+        configuration.setExcludeRules([rule] as Set)
+
+        then:
+        configuration.excludeRules == [rule] as Set
+    }
+
+    def "can extend multiple configurations"() {
+        def configuration = conf()
+        def configuration1 = conf("otherConf1")
+        def configuration2 = conf("otherConf2")
+
+        when:
+        configuration.extendsFrom(configuration1)
+
+        then:
+        configuration.extendsFrom == [configuration1] as Set
+
+        when:
+        configuration.extendsFrom(configuration2);
+
+        then:
+        configuration.extendsFrom == [configuration1, configuration2] as Set
+
+        when:
+        def configuration3 = conf("replacedConf")
+        configuration.setExtendsFrom([configuration3])
+
+        then:
+        configuration.extendsFrom == [configuration3] as Set
+    }
+
+    def "extended configurations are not duplicated"() {
+        def configuration = conf()
+        def configuration1 = conf("other")
+
+        when:
+        configuration.extendsFrom(configuration1, configuration1)
+
+        then:
+        configuration.extendsFrom == [configuration1] as Set
+    }
+
+    def "reports direct cycle in configurations"() {
+        def configuration = conf()
+        def otherConf = conf("other")
+        configuration.extendsFrom(otherConf)
+
+        when:
+        otherConf.extendsFrom(configuration)
+
+        then:
+        thrown InvalidUserDataException
+    }
+
+    def "reports indirect cycle in extended configurations"() {
+        def configuration = conf()
+        def conf1 = conf("other")
+        def conf2 = conf("other2")
+
+        when:
+        configuration.extendsFrom(conf1)
+        conf1.extendsFrom(conf2)
+        conf2.extendsFrom(configuration)
+
+        then:
+        thrown InvalidUserDataException
+    }
+
+    def "reports cycle introduced by setExtends"() {
+        def configuration = conf()
+        def otherConf = conf("other")
+        configuration.extendsFrom(otherConf)
+
+        when:
+        otherConf.setExtendsFrom([configuration])
+
+        then:
+        thrown InvalidUserDataException
+    }
+
+    def "creates hierarchy"() {
+        def root1 = conf("root1")
+        def middle1 = conf("middle1").extendsFrom(root1)
+        def root2 = conf("root2")
+        def middle2 = conf("middle2").extendsFrom(root2)
+        def leaf = conf("leaf1").extendsFrom(middle1, middle2)
+
+        when:
+        def hierarchy = leaf.hierarchy
+
+        then:
+        hierarchy.size() == 5
+        hierarchy.iterator().next() == leaf
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle1, root1);
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle2, root2);
+    }
+
+    private static void assertBothExistsAndOneIsBeforeOther(Set<Configuration> hierarchy, Configuration beforeConf, Configuration afterConf) {
+        assert hierarchy.contains(beforeConf)
+        assert hierarchy.contains(afterConf)
+
+        boolean foundBeforeConf = false;
+        for (Configuration configuration : hierarchy) {
+            if (configuration.equals(beforeConf)) {
+                foundBeforeConf = true;
+            }
+            if (configuration.equals(afterConf)) {
+                assertThat(foundBeforeConf, equalTo(true));
+            }
+        }
+    }
+
+    def "get dependencies"() {
+        def configuration = conf()
+        def dependency = Mock(Dependency)
+        def projectDependency = Mock(ProjectDependency.class);
+
+        when:
+        configuration.dependencies.add(dependency)
+        configuration.dependencies.add(projectDependency)
+
+        then:
+        configuration.dependencies as Set == [dependency, projectDependency] as Set
+        configuration.dependencies.withType(ProjectDependency) as Set == [projectDependency] as Set
+        configuration.dependencies.withType(SelfResolvingDependency) as Set == [projectDependency] as Set
+    }
+
+    def "get all dependencies"() {
+        def parentConf = conf("parent")
+        def configuration = conf().extendsFrom(parentConf)
+        def dependency = Mock(Dependency)
+        def projectDependency = Mock(ProjectDependency.class);
+
+        when:
+        parentConf.dependencies.add(dependency)
+        configuration.dependencies.add(projectDependency)
+
+        then:
+        configuration.dependencies as Set == [projectDependency] as Set
+        configuration.allDependencies as Set == [dependency, projectDependency] as Set
+    }
+
+    def "resolves files"() {
+        def configuration = conf()
+        def fileSet = [new File("somePath")] as Set
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+
+        given:
+        expectResolved(resolvedConfiguration);
+
+        and:
+        resolvedConfiguration.hasError() >> false
+        resolvedConfiguration.getFiles(_) >> fileSet
+
+        when:
+        def resolved = configuration.resolve()
+
+        then:
+        resolved == fileSet
+        configuration.state == RESOLVED
+    }
+
+    def "get as path throws failure resolving"() {
+        def configuration = conf()
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+        def failure = new RuntimeException()
+
+        given:
+        expectResolved(resolvedConfiguration);
+
+        and:
+        resolvedConfiguration.hasError() >> true
+        resolvedConfiguration.rethrowFailure() >> { throw failure }
+
+        when:
+        configuration.getResolvedConfiguration()
+
+        then:
+        configuration.getState() == RESOLVED_WITH_FAILURES
+
+        when:
+        configuration.resolve()
+
+        then:
+        def t = thrown(RuntimeException)
+        t == failure
+    }
+
+    def "state indicates failure resolving graph"() {
+        given:
+        def configuration = conf()
+        def failure = new ResolveException(configuration, new RuntimeException())
+
+        and:
+        _ * resolver.resolve(_, _) >> { ConfigurationInternal config, ResolverResults resolverResults ->
+            resolverResults.failed(failure)
+        }
+        _ * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> true
+
+        when:
+        configuration.getBuildDependencies()
+
+        then:
+        def t = thrown(ResolveException)
+        t == failure
+        configuration.getState() == RESOLVED_WITH_FAILURES
+    }
+
+    def fileCollectionWithDependencies() {
+        def dependency1 = dependency("group1", "name", "version");
+        def dependency2 = dependency("group2", "name", "version");
+        def configuration = conf()
+
+        when:
+        def fileCollection = configuration.fileCollection(dependency1)
+
+        then:
+        fileCollection.getDependencySpec().isSatisfiedBy(dependency1)
+        !fileCollection.getDependencySpec().isSatisfiedBy(dependency2)
+    }
+
+    def fileCollectionWithSpec() {
+        def configuration = conf()
+        Spec<Dependency> spec = Mock(Spec)
+
+        when:
+        def fileCollection = configuration.fileCollection(spec)
+
+        then:
+        fileCollection.getDependencySpec() == spec
+    }
+
+    def fileCollectionWithClosureSpec() {
+        def closure = { dep -> dep.group == 'group1' }
+        def configuration = conf()
+
+        when:
+        def fileCollection = configuration.fileCollection(closure)
+
+        then:
+        fileCollection.getDependencySpec().isSatisfiedBy(dependency("group1", "name", "version"))
+        !fileCollection.getDependencySpec().isSatisfiedBy(dependency("group2", "name", "version"))
+    }
+
+    def filesWithDependencies() {
+        def configuration = conf()
+        def fileSet = [new File("somePath")] as Set
+
+        when:
+        prepareForFilesBySpec(fileSet)
+
+        then:
+        configuration.files(Mock(Dependency)) == fileSet
+        configuration.state == RESOLVED
+    }
+
+    def filesWithSpec() {
+        def configuration = conf()
+        def fileSet = [new File("somePath")] as Set
+
+        when:
+        prepareForFilesBySpec(fileSet)
+
+        then:
+        configuration.files(Mock(Spec)) == fileSet
+        configuration.state == RESOLVED
+    }
+
+    def filesWithClosureSpec() {
+        def configuration = conf()
+        def closure = { dep -> dep.group == 'group1' }
+        def fileSet = [new File("somePath")] as Set
+
+        when:
+        prepareForFilesBySpec(fileSet);
+
+        then:
+        configuration.files(closure) == fileSet
+        configuration.state == RESOLVED
+    }
+
+    def "resolves as resolved configuration"() {
+        def configuration = conf()
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+
+        given:
+        expectResolved(resolvedConfiguration)
+
+        when:
+        def r = configuration.getResolvedConfiguration()
+
+        then:
+        r == resolvedConfiguration
+        configuration.state == RESOLVED
+    }
+
+    def "multiple resolves use cached result"() {
+        def configuration = conf()
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+
+        given:
+        expectResolved(resolvedConfiguration)
+
+        when:
+        def r = configuration.getResolvedConfiguration()
+
+        then:
+        configuration.getResolvedConfiguration() == r
+    }
+
+    private prepareForFilesBySpec(Set<File> fileSet) {
+        def resConfig = Mock(ResolvedConfiguration)
+        expectResolved(resConfig)
+        1 * resConfig.getFiles(_ as Spec) >> fileSet
+    }
+
+    private void expectResolved(ResolvedConfiguration resolvedConfiguration) {
+        def resolutionResults = Mock(ResolutionResult)
+        def projectConfigurationResults = Mock(ResolvedProjectConfigurationResults)
+
+        _ * projectConfigurationResults.get() >> Collections.emptySet()
+        _ * resolver.resolve(_, _) >> { ConfigurationInternal config, ResolverResults resolverResults ->
+            resolverResults.resolved(resolutionResults, projectConfigurationResults)
+            resolverResults.withResolvedConfiguration(resolvedConfiguration)
+        }
+    }
+
+    def "artifacts have correct build dependencies"() {
+        def configuration = conf()
+        def artifactTask2 = Mock(Task)
+        def artifactTask1 = Mock(Task)
+
+        given:
+        def otherConfiguration = conf("otherConf")
+
+        def artifact1 = artifact("name1")
+        artifact1.builtBy(artifactTask1)
+
+        def artifact2 = artifact("name2")
+        artifact2.builtBy(artifactTask2)
+
+        when:
+        configuration.artifacts.add(artifact1)
+        otherConfiguration.artifacts.add(artifact2)
+        configuration.extendsFrom(otherConfiguration)
+
+        then:
+        configuration.allArtifacts.files.files == [artifact1.file, artifact2.file] as Set
+        configuration.allArtifacts.files.buildDependencies == configuration.allArtifacts.buildDependencies
+        configuration.allArtifacts.buildDependencies.getDependencies(Mock(Task)) == [artifactTask1, artifactTask2] as Set
+    }
+
+    def "build dependencies delegates to self resolving dependencies"() {
+        def configuration = conf()
+        def targetTask = Mock(Task)
+        def dependentTasks = [Mock(Task)] as Set
+        def taskDependency = Mock(TaskDependency)
+        def selfResolvingDependency = Mock(FileCollectionDependency)
+
+        given:
+        _ * selfResolvingDependency.buildDependencies >> taskDependency
+        _ * taskDependency.getDependencies(targetTask) >> dependentTasks
+
+        and:
+        expectResolved(Mock(ResolvedConfiguration.class))
+
+        when:
+        configuration.getDependencies().add(selfResolvingDependency);
+
+        then:
+        configuration.buildDependencies.getDependencies(targetTask) == dependentTasks
+    }
+
+    def "configuration build dependency delegates to inherited configurations"() {
+        def otherConf = conf("other")
+        def configuration = conf().extendsFrom(otherConf)
+        def fileCollectionDependency = Mock(FileCollectionDependency.class);
+        def taskDependency = Mock(TaskDependency)
+
+        def targetTask = Mock(Task)
+        def dependentTasks = [Mock(Task)] as Set
+
+        given:
+        _ * fileCollectionDependency.buildDependencies >> taskDependency
+        _ * taskDependency.getDependencies(targetTask) >> dependentTasks
+
+        and:
+        expectResolved(Mock(ResolvedConfiguration.class))
+
+        when:
+        otherConf.dependencies.add(fileCollectionDependency)
+        configuration.extendsFrom(otherConf)
+
+        then:
+        otherConf.buildDependencies.getDependencies(targetTask) == dependentTasks
+        configuration.buildDependencies.getDependencies(targetTask) == dependentTasks
+    }
+
+    def "task dependency from project dependency wihtout common configuration"() {
+        // This test exists because a NullPointerException was thrown by getTaskDependencyFromProjectDependency()
+        // if the rootProject defined a task as the same name as a subproject task, but did not define the same configuration.
+
+        def configuration = conf()
+
+        def mainTask = Mock(Task)
+        def rootProject = Mock(Project)
+        def taskProject = Mock(Project)
+        mainTask.project >> taskProject
+        taskProject.rootProject >> rootProject
+
+        def otherTask = Mock(Task)
+        def otherTaskSet = [otherTask] as Set
+        def dependentProject = Mock(Project)
+        otherTask.project >> dependentProject
+
+        when:
+        def td = configuration.getTaskDependencyFromProjectDependency(false, "testit")
+
+        and:
+        rootProject.getTasksByName("testit", true) >> otherTaskSet
+
+        and:
+        def configurationContainer = Mock(ConfigurationContainer)
+        1 * dependentProject.configurations >> configurationContainer
+        1 * configurationContainer.findByName(configuration.name) >> null
+
+        then:
+        td.getDependencies(mainTask) == [] as Set
     }
 
     def "all artifacts collection has immediate artifacts"() {
@@ -137,6 +606,130 @@ class DefaultConfigurationSpec extends Specification {
         removed == ["p1p1-1", "p1p2-1"]
     }
 
+    def "artifactAdded action is fired"() {
+        def configuration = conf()
+        def addedAction = Mock(Action)
+        def removedAction = Mock(Action)
+
+        def addedArtifact = artifact()
+
+        given:
+        configuration.artifacts.whenObjectAdded(addedAction)
+        configuration.artifacts.whenObjectRemoved(removedAction)
+
+        when:
+        configuration.artifacts.add(addedArtifact)
+
+        then:
+        1 * addedAction.execute(addedArtifact)
+        0 * removedAction._
+
+        and:
+        configuration.artifacts.size() == 1
+
+        when:
+        def unknownArtifact = artifact("other")
+        configuration.artifacts.remove(unknownArtifact)
+
+        then:
+        0 * _._
+        configuration.artifacts.size() == 1
+
+        when:
+        configuration.artifacts.removeAll(addedArtifact)
+
+        then:
+        1 * removedAction.execute(addedArtifact)
+        0 * addedAction._
+
+        and:
+        configuration.artifacts.empty
+    }
+
+    def "can copy"() {
+        def configuration = prepareConfigurationForCopyTest()
+
+        def resolutionStrategyCopy = Mock(ResolutionStrategyInternal)
+
+        when:
+        1 * resolutionStrategy.copy() >> resolutionStrategyCopy
+
+        and:
+        def copiedConfiguration = configuration.copy()
+
+        then:
+        checkCopiedConfiguration(configuration, copiedConfiguration, resolutionStrategyCopy)
+        assert copiedConfiguration.dependencies == configuration.dependencies
+        assert copiedConfiguration.extendsFrom.empty
+    }
+
+    def "can copy with spec"() {
+        def configuration = prepareConfigurationForCopyTest()
+        def resolutionStrategyCopy = Mock(ResolutionStrategyInternal)
+        configuration.getDependencies().add(dependency("group3", "name3", "version3"));
+
+        when:
+        1 * resolutionStrategy.copy() >> resolutionStrategyCopy
+
+        and:
+        def copiedConfiguration = configuration.copy(new Spec<Dependency>() {
+            public boolean isSatisfiedBy(Dependency element) {
+                return !element.getGroup().equals("group3");
+            }
+        })
+
+        then:
+        checkCopiedConfiguration(configuration, copiedConfiguration, resolutionStrategyCopy)
+        assert copiedConfiguration.dependencies.collect({it.group}) == ["group1", "group2"]
+    }
+
+    def "can copy recursive"() {
+        def resolutionStrategyCopy = Mock(ResolutionStrategyInternal)
+        def configuration = prepareConfigurationForCopyTest()
+
+
+        when:
+        1 * resolutionStrategy.copy() >> resolutionStrategyCopy
+
+        and:
+        def copiedConfiguration = configuration.copyRecursive()
+
+        then:
+        checkCopiedConfiguration(configuration, copiedConfiguration, resolutionStrategyCopy)
+        assert copiedConfiguration.dependencies == configuration.allDependencies
+        assert copiedConfiguration.extendsFrom.empty
+    }
+
+    private prepareConfigurationForCopyTest() {
+        def configuration = conf()
+        configuration.visible = false
+        configuration.transitive = false
+        configuration.description = "descript"
+        configuration.exclude([group: "value"])
+        configuration.exclude([group: "value2"]);
+        configuration.artifacts.add(artifact("name1", "ext1", "type1", "classifier1"))
+        configuration.artifacts.add(artifact("name2", "ext2", "type2", "classifier2"))
+        configuration.dependencies.add(dependency("group1", "name1", "version1"))
+        configuration.dependencies.add(dependency("group2", "name2", "version2"))
+
+        def otherConf = conf("other")
+        otherConf.dependencies.add(dependency("otherGroup", "name3", "version3"))
+        configuration.extendsFrom(otherConf)
+        return configuration
+    }
+
+    private void checkCopiedConfiguration(Configuration original, Configuration copy, def resolutionStrategyInCopy) {
+        assert copy.name == original.name + "Copy"
+        assert copy.visible == original.visible
+        assert copy.transitive == original.transitive
+        assert copy.description == original.description
+        assert copy.allArtifacts as Set == original.allArtifacts as Set
+        assert copy.excludeRules == original.excludeRules
+        assert copy.resolutionStrategy == resolutionStrategyInCopy
+        true
+    }
+
+
     def "incoming dependencies set has same name and path as owner configuration"() {
         def config = conf("conf", ":path")
 
@@ -183,7 +776,7 @@ class DefaultConfigurationSpec extends Specification {
         files.files
 
         then:
-        interaction { resolvedConfiguration(config) }
+        interaction { resolveConfig(config) }
         0 * resolver._
     }
 
@@ -192,6 +785,9 @@ class DefaultConfigurationSpec extends Specification {
         Task task = Mock()
         TaskDependency taskDep = Mock()
         def config = conf("conf")
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+        def resolverResults = new ResolverResults()
+        def projectConfigurationResults = Mock(ResolvedProjectConfigurationResults)
 
         given:
         config.dependencies.add(dependency)
@@ -203,6 +799,12 @@ class DefaultConfigurationSpec extends Specification {
         then:
         depTaskDeps == [task] as Set
         fileTaskDeps == [task] as Set
+        _ * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> false
+        _ * resolvedConfiguration.hasError() >> false
+        _ * resolver.resolve(config, _) >> { ConfigurationInternal conf, ResolverResults res ->
+            res.resolved(Mock(ResolutionResult), projectConfigurationResults)
+        }
+        _ * projectConfigurationResults.get() >> []
         _ * dependency.buildDependencies >> taskDep
         _ * taskDep.getDependencies(_) >> ([task] as Set)
         0 * _._
@@ -219,13 +821,13 @@ class DefaultConfigurationSpec extends Specification {
         config.resolvedConfiguration
 
         then:
-        interaction { resolvedConfiguration(config) }
+        interaction { resolveConfig(config) }
         1 * action.execute(config.incoming)
     }
 
     def "calls beforeResolve closure on incoming dependencies set when dependencies are resolved"() {
         def config = conf("conf")
-        resolvedConfiguration(config)
+        resolveConfig(config)
         def called = false
 
         expect:
@@ -252,14 +854,14 @@ class DefaultConfigurationSpec extends Specification {
         config.resolvedConfiguration
 
         then:
-        interaction { resolvedConfiguration(config) }
+        interaction { resolveConfig(config) }
         1 * action.execute(config.incoming)
 
     }
 
     def "calls afterResolve closure on incoming dependencies set when dependencies are resolved"() {
         def config = conf("conf")
-        resolvedConfiguration(config)
+        resolveConfig(config)
         def called = false
 
         expect:
@@ -274,24 +876,24 @@ class DefaultConfigurationSpec extends Specification {
         then:
         called
     }
-    
+
     def "a recursive copy of a configuration includes inherited exclude rules"() {
         given:
         def (p1, p2, child) = [conf("p1"), conf("p2"), conf("child")]
         child.extendsFrom p1, p2
-        
+
         and:
         def (p1Exclude, p2Exclude) = [[group: 'p1', module: 'p1'], [group: 'p2', module: 'p2']]
         p1.exclude p1Exclude
         p2.exclude p2Exclude
-        
+
         when:
         def copied = child.copyRecursive()
-        
+
         then:
         1 * resolutionStrategy.copy() >> Mock(ResolutionStrategyInternal)
         copied.excludeRules.size() == 2
-        copied.excludeRules.collect{[group: it.group, module: it.module]}.sort { it.group } == [p1Exclude, p2Exclude]
+        copied.excludeRules.collect { [group: it.group, module: it.module] }.sort { it.group } == [p1Exclude, p2Exclude]
     }
 
     def "copied configuration has own instance of resolution strategy"() {
@@ -310,26 +912,214 @@ 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, Mock(ResolvedProjectConfigurationResults))
+
+        resolves(config, result, Mock(ResolvedConfiguration))
 
         when:
         def out = config.incoming.resolutionResult
 
         then:
-        1 * resolver.resolve(config) >> resolverResults
         out == result
     }
 
+    def resolves(ConfigurationInternal config, ResolutionResult resolutionResult, ResolvedConfiguration resolvedConfiguration) {
+        def projectConfigurationResults = Mock(ResolvedProjectConfigurationResults)
+        projectConfigurationResults.get() >> []
+        resolver.resolve(config, _) >> { ConfigurationInternal conf, ResolverResults res ->
+            res.resolved(resolutionResult, projectConfigurationResults)
+        }
+        resolver.resolveArtifacts(config, _) >> { ConfigurationInternal conf, ResolverResults res ->
+            res.withResolvedConfiguration(resolvedConfiguration)
+        }
+    }
+
+    def "resolving configuration for task dependencies puts it into the right state"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+        resolves(config, result, Mock(ResolvedConfiguration))
+
+        when:
+        1 * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> true
+        config.getBuildDependencies()
+
+        then:
+        config.resolvedState == ConfigurationInternal.InternalState.TASK_DEPENDENCIES_RESOLVED
+        config.state == RESOLVED
+    }
+
+    def "can determine task dependencies without resolution"() {
+        def config = conf("conf")
+
+        when:
+        config.getBuildDependencies()
+
+        then:
+        config.resolvedState == ConfigurationInternal.InternalState.UNRESOLVED
+        config.state == UNRESOLVED
+
+        and:
+        1 * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> false
+        0 * _._
+    }
+
+    def "resolving configuration marks parent configuration as observed"() {
+        def parent = conf("parent", ":parent")
+        def config = conf("conf")
+        config.extendsFrom parent
+        def result = Mock(ResolutionResult)
+        resolves(config, result, Mock(ResolvedConfiguration))
+
+        when:
+        config.resolve()
+
+        then:
+        parent.observedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+    }
+
+    def "resolving configuration puts it into the right state and broadcasts events"() {
+        def listenerBroadcaster = Mock(ListenerBroadcast)
+
+        when:
+        def config = conf("conf")
+
+        then:
+        1 * listenerManager.createAnonymousBroadcaster(_) >> listenerBroadcaster
+
+        def listener = Mock(DependencyResolutionListener)
+
+        when:
+        def result = Mock(ResolutionResult)
+        resolves(config, result, Mock(ResolvedConfiguration))
+        config.incoming.getResolutionResult()
+
+        then:
+        _ * listenerBroadcaster.getSource() >> listener
+        1 * listener.beforeResolve(config.incoming)
+        1 * listener.afterResolve(config.incoming)
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+    }
+
+    def "resolving configuration for task dependencies, and then resolving it for results does not re-resolve configuration"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+        resolves(config, result, Mock(ResolvedConfiguration))
+
+        given:
+        _ * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> true
+
+        when:
+        config.getBuildDependencies()
+
+        then:
+        config.resolvedState == ConfigurationInternal.InternalState.TASK_DEPENDENCIES_RESOLVED
+        config.state == RESOLVED
+
+        when:
+        config.incoming.getResolutionResult()
+
+        then:
+        0 * resolver.resolve(_)
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+    }
+
+    def "resolving configuration for results, and then resolving it for task dependencies does not re-resolve configuration"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+
+        given:
+        _ * resolutionStrategy.resolveGraphToDetermineTaskDependencies() >> true
+
+        when:
+        resolves(config, result, Mock(ResolvedConfiguration))
+        config.incoming.getResolutionResult()
+
+        then:
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+
+        when:
+        config.getBuildDependencies()
+
+        then:
+        0 * resolver.resolve(_)
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+    }
+
+    def "resolving configuration twice returns the same result objects"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+        def resolvedFiles = Mock(Set)
+
+        when:
+        resolves(config, result, resolvedConfiguration)
+
+        def previousFiles = config.files
+        def previousResolutionResult = config.incoming.resolutionResult
+        def previousResolvedConfiguration = config.resolvedConfiguration
+
+        then:
+        1 * resolvedConfiguration.getFiles(_) >> resolvedFiles
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+
+        when:
+        def nextFiles = config.files
+        def nextResolutionResult = config.incoming.resolutionResult
+        def nextResolvedConfiguration = config.resolvedConfiguration
+
+        then:
+        0 * resolver.resolve(_)
+        1 * resolvedConfiguration.getFiles(_) >> resolvedFiles
+        config.resolvedState == ConfigurationInternal.InternalState.RESULTS_RESOLVED
+        config.state == RESOLVED
+
+        // We get back the same resolution results
+        previousResolutionResult == result
+        nextResolutionResult == result
+
+        // We get back the same resolved configuration
+        previousResolvedConfiguration == resolvedConfiguration
+        nextResolvedConfiguration == resolvedConfiguration
+
+        // And the same files
+        previousFiles == resolvedFiles
+        nextFiles == resolvedFiles
+    }
+
+    def "copied configuration is not resolved"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+
+        given:
+        resolves(config, result, Mock(ResolvedConfiguration))
+
+        config.incoming.resolutionResult
+
+        when:
+        def copy = config.copy()
+
+        then:
+        1 * resolutionStrategy.copy() >> Mock(ResolutionStrategyInternal)
+        copy.resolvedState == ConfigurationInternal.InternalState.UNRESOLVED
+        copy.state == UNRESOLVED
+    }
+
     def "provides task dependency from project dependency using 'needed'"() {
         def conf = conf("conf")
-        when: def dep = conf.getTaskDependencyFromProjectDependency(true, "foo") as TasksFromProjectDependencies
-        then: dep.taskName == "foo"
+        when:
+        def dep = conf.getTaskDependencyFromProjectDependency(true, "foo") as TasksFromProjectDependencies
+        then:
+        dep.taskName == "foo"
     }
 
     def "provides task dependency from project dependency using 'dependents'"() {
         def conf = conf("conf")
-        when: def dep = conf.getTaskDependencyFromProjectDependency(false, "bar") as TasksFromDependentProjects
+        when:
+        def dep = conf.getTaskDependencyFromProjectDependency(false, "bar") as TasksFromDependentProjects
         then:
         dep.taskName == "bar"
         dep.configurationName == "conf"
@@ -338,22 +1128,192 @@ class DefaultConfigurationSpec extends Specification {
     def "mutations are prohibited after resolution"() {
         def conf = conf("conf")
         def result = Mock(ResolutionResult)
-        def resolverResults = new ResolverResults()
-        resolverResults.resolved(Mock(ResolvedConfiguration), result, Mock(ResolvedProjectConfigurationResults))
 
-        when:
+        given:
+        resolves(conf, result, Mock(ResolvedConfiguration))
         conf.incoming.getResolutionResult()
-        then:
-        1 * resolver.resolve(conf) >> resolverResults
 
-        when: conf.dependencies.add(Mock(Dependency))
+        when:
+        conf.dependencies.add(Mock(Dependency))
         then:
         def exDependency = thrown(InvalidUserDataException);
-        exDependency.message == "Cannot change configuration ':conf' after it has been resolved."
+        exDependency.message == "Cannot change dependencies of configuration ':conf' after it has been resolved."
 
-        when: conf.artifacts.add(Mock(PublishArtifact))
+        when:
+        conf.artifacts.add(Mock(PublishArtifact))
         then:
         def exArtifact = thrown(InvalidUserDataException);
-        exArtifact.message == "Cannot change configuration ':conf' after it has been resolved."
+        exArtifact.message == "Cannot change artifacts of configuration ':conf' after it has been resolved."
+    }
+
+    def "defaultDependencies action does not trigger when config has dependencies"() {
+        def conf = conf("conf")
+        def defaultDependencyAction = Mock(Action)
+        conf.defaultDependencies defaultDependencyAction
+        conf.dependencies.add(Mock(Dependency))
+
+        when:
+        conf.triggerWhenEmptyActionsIfNecessary()
+
+        then:
+        0 * _
+    }
+
+    def "second defaultDependencies action does not trigger if first one already added dependencies"() {
+        def conf = conf("conf")
+        def defaultDependencyAction1 = Mock(Action)
+        def defaultDependencyAction2 = Mock(Action)
+        conf.defaultDependencies defaultDependencyAction1
+        conf.defaultDependencies defaultDependencyAction2
+
+        when:
+        conf.triggerWhenEmptyActionsIfNecessary()
+
+        then:
+        1 * defaultDependencyAction1.execute(conf.dependencies) >> {
+            conf.dependencies.add(Mock(Dependency))
+        }
+        0 * _
+    }
+
+    def "defaultDependencies action is called even if parent config has dependencies"() {
+        def parent = conf("parent", ":parent")
+        parent.dependencies.add(Mock(Dependency))
+
+        def conf = conf("conf")
+        def defaultDependencyAction = Mock(Action)
+        conf.extendsFrom parent
+        conf.defaultDependencies defaultDependencyAction
+
+        when:
+        conf.triggerWhenEmptyActionsIfNecessary()
+
+        then:
+        1 * defaultDependencyAction.execute(conf.dependencies)
+        0 * _
+    }
+
+    def "defaultDependencies action is called on self first, then on parent"() {
+        def parentWhenEmptyAction = Mock(Action)
+        def parent = conf("parent", ":parent")
+        parent.defaultDependencies parentWhenEmptyAction
+
+        def conf = conf("conf")
+        def defaultDependencyAction = Mock(Action)
+        conf.extendsFrom parent
+        conf.defaultDependencies defaultDependencyAction
+
+        when:
+        conf.triggerWhenEmptyActionsIfNecessary()
+
+        then:
+        1 * defaultDependencyAction.execute(conf.dependencies)
+
+        then:
+        1 * parentWhenEmptyAction.execute(parent.dependencies)
+        0 * _
+    }
+
+    def propertyChangeWithNonUnresolvedStateShouldThrowEx() {
+        def configuration = conf()
+        prepareForFilesBySpec([] as Set)
+
+        given:
+        configuration.resolve();
+
+        when: configuration.setTransitive(true)
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.setVisible(false)
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.exclude([:])
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.setExcludeRules([] as Set)
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.extendsFrom(conf("other"))
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.dependencies.add(Mock(Dependency))
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.dependencies.remove(Mock(Dependency))
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.artifacts.add(artifact())
+        then: thrown(InvalidUserDataException)
+
+        when: configuration.artifacts.remove(artifact())
+        then: thrown(InvalidUserDataException)
+    }
+
+    def dumpString() {
+        when:
+        def configurationDependency = dependency("dumpgroup1", "dumpname1", "dumpversion1");
+        def otherConfSimilarDependency = dependency("dumpgroup1", "dumpname1", "dumpversion1");
+        def otherConfDependency = dependency("dumpgroup2", "dumpname2", "dumpversion2");
+        def otherConf = conf("dumpConf");
+        otherConf.getDependencies().add(otherConfDependency);
+        otherConf.getDependencies().add(otherConfSimilarDependency);
+
+        def configuration = conf().extendsFrom(otherConf)
+        configuration.getDependencies().add(configurationDependency);
+
+        then:
+        configuration.dump() == """
+Configuration:  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration'  name='conf'  hashcode='${configuration.hashCode()}'
+Local Dependencies:
+   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}
+Local Artifacts:
+   none
+All Dependencies:
+   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}
+   DefaultExternalModuleDependency{group='dumpgroup2', name='dumpname2', version='dumpversion2', configuration='default'}
+All Artifacts:
+   none"""
+    }
+
+
+    // You need to wrap this in an interaction {} block when calling it
+    private ResolvedConfiguration resolveConfig(ConfigurationInternal config, ConfigurationResolver dependencyResolver = resolver) {
+        def resolvedConfiguration = Mock(ResolvedConfiguration)
+        def resolutionResult = Mock(ResolutionResult)
+
+        resolves(config, resolutionResult, resolvedConfiguration)
+        resolvedConfiguration
+    }
+
+    private dependency(String group, String name, String version) {
+        new DefaultExternalModuleDependency(group, name, version);
+    }
+
+    private DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
+        new DefaultConfiguration(path, confName, configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy, projectAccessListener, projectFinder)
+    }
+
+    private DefaultPublishArtifact artifact(String name) {
+        artifact(name, "ext", "type", "classy")
+    }
+
+    private DefaultPublishArtifact artifact(String name, String extension, String type, String classifier) {
+        return new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(name));
+    }
+
+    private DefaultPublishArtifact artifact(Map props = [:]) {
+        new DefaultPublishArtifact(
+                props.name ?: "artifact",
+                props.extension ?: "artifact",
+                props.type,
+                props.classifier,
+                props.date,
+                props.file,
+                props.tasks ?: []
+        )
+    }
+
+    private PublishArtifactSet artifacts(PublishArtifact... containedArtifacts) {
+        new DefaultPublishArtifactSet("artifacts", WrapUtil.toDomainObjectSet(PublishArtifact.class, containedArtifacts))
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
deleted file mode 100644
index ca5131e..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
+++ /dev/null
@@ -1,906 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-import groovy.lang.Closure;
-import org.gradle.api.GradleException;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.artifacts.result.ResolutionResult;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.*;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResults;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.api.tasks.TaskDependency;
-import org.gradle.internal.event.ListenerBroadcast;
-import org.gradle.internal.event.ListenerManager;
-import org.gradle.util.*;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.*;
-
-import static org.gradle.util.Matchers.hasSameItems;
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultConfigurationTest {
-
-    //TODO **** if you touch this class, try pushing out more coverage into new DefaultConfigurationSpec ****
-
-    private JUnit4Mockery context = new JUnit4GroovyMockery();
-    private ConfigurationResolver dependencyResolver = context.mock(ConfigurationResolver.class);
-    private ConfigurationsProvider configurationContainer;
-    private ListenerManager listenerManager = context.mock(ListenerManager.class);
-    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class);
-    private DefaultConfiguration configuration;
-    private DependencyResolutionListener dependencyResolutionBroadcast = context.mock(DependencyResolutionListener.class);
-    private ListenerBroadcast resolutionListenerBroadcast = context.mock(ListenerBroadcast.class); 
-
-    @Before
-    public void setUp() {
-        configurationContainer = context.mock(ConfigurationsProvider.class);
-        context.checking(new Expectations(){{
-            allowing(listenerManager).createAnonymousBroadcaster(DependencyResolutionListener.class);
-            will(returnValue(resolutionListenerBroadcast));
-            allowing(resolutionListenerBroadcast).getSource();
-            will(returnValue(dependencyResolutionBroadcast));
-            allowing(dependencyResolutionBroadcast).afterResolve(with(any(ResolvableDependencies.class)));
-            allowing(dependencyResolutionBroadcast).beforeResolve(with(any(ResolvableDependencies.class)));
-            will(returnValue(null));
-        }});
-        configuration = createNamedConfiguration("path", "name");
-    }
-
-    @Test
-    public void defaultValues() {
-        assertThat(configuration.getName(), equalTo("name"));
-        assertThat(configuration.isVisible(), equalTo(true));
-        assertThat(configuration.getExtendsFrom().size(), equalTo(0));
-        assertThat(configuration.isTransitive(), equalTo(true));
-        assertThat(configuration.getDescription(), nullValue());
-        assertThat(configuration.getState(), equalTo(Configuration.State.UNRESOLVED));
-        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
-    }
-
-    @Test
-    public void hasUsefulDisplayName() {
-        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
-        assertThat(configuration.toString(), equalTo("configuration 'path'"));
-        assertThat(configuration.getIncoming().toString(), equalTo("dependencies 'path'"));
-    }
-
-    @Test
-    public void withPrivateVisibility() {
-        configuration.setVisible(false);
-        assertFalse(configuration.isVisible());
-    }
-
-    @Test
-    public void withIntransitive() {
-        configuration.setTransitive(false);
-        assertFalse(configuration.isTransitive());
-    }
-
-    @Test
-    public void exclude() {
-        Map<String, String> excludeArgs1 = toMap("group", "aGroup");
-        Map<String, String> excludeArgs2 = toMap("module", "aModule");
-        assertThat(configuration.exclude(excludeArgs1), sameInstance(configuration));
-        configuration.exclude(excludeArgs2);
-        assertThat(configuration.getExcludeRules(), equalTo(WrapUtil.<ExcludeRule>toSet(
-                new DefaultExcludeRule("aGroup", null), new DefaultExcludeRule(null, "aModule"))));
-    }
-
-    @Test
-    public void setExclude() {
-        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule("groupValue", null));
-        configuration.setExcludeRules(excludeRules);
-        assertThat(configuration.getExcludeRules(), equalTo(excludeRules));
-    }
-
-    @Test
-    public void withDescription() {
-        configuration.setDescription("description");
-        assertThat(configuration.getDescription(), equalTo("description"));
-    }
-
-    @Test
-    public void extendsOtherConfigurations() {
-        Configuration configuration1 = createNamedConfiguration("otherConf1");
-        configuration.extendsFrom(configuration1);
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
-
-        Configuration configuration2 = createNamedConfiguration("otherConf2");
-        configuration.extendsFrom(configuration2);
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1, configuration2)));
-    }
-
-    @Test
-    public void setExtendsFrom() {
-        Configuration configuration1 = createNamedConfiguration("otherConf1");
-
-        configuration.setExtendsFrom(toSet(configuration1));
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
-
-        Configuration configuration2 = createNamedConfiguration("otherConf2");
-        configuration.setExtendsFrom(toSet(configuration2));
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration2)));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void extendsFromWithDirectCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration = createNamedConfiguration("otherConf");
-        otherConfiguration.extendsFrom(configuration);
-        configuration.extendsFrom(otherConfiguration);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void extendsFromWithIndirectCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration1 = createNamedConfiguration("otherConf1");
-        Configuration otherConfiguration2 = createNamedConfiguration("otherConf2");
-        configuration.extendsFrom(otherConfiguration1);
-        otherConfiguration1.extendsFrom(otherConfiguration2);
-        otherConfiguration2.extendsFrom(configuration);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void setExtendsFromWithCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration = createNamedConfiguration("otherConf");
-        otherConfiguration.extendsFrom(configuration);
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-    }
-
-    @Test
-    public void getHierarchy() {
-        Configuration root1 = createNamedConfiguration("root1");
-        Configuration middle1 = createNamedConfiguration("middle1").extendsFrom(root1);
-        Configuration root2 = createNamedConfiguration("root2");
-        Configuration middle2 = createNamedConfiguration("middle2").extendsFrom(root1, root2);
-        createNamedConfiguration("root3");
-        Configuration leaf = createNamedConfiguration("leaf1").extendsFrom(middle1, middle2);
-        Set<Configuration> hierarchy = leaf.getHierarchy();
-        assertThat(hierarchy.size(), equalTo(5));
-        assertThat(hierarchy.iterator().next(), equalTo(leaf));
-        assertBothExistsAndOneIsBeforeOther(hierarchy, middle1, root1);
-        assertBothExistsAndOneIsBeforeOther(hierarchy, middle2, root2);
-    }
-
-    private void assertBothExistsAndOneIsBeforeOther(Set<Configuration> hierarchy, Configuration beforeConf, Configuration afterConf) {
-        assertThat(hierarchy, hasItem(beforeConf));
-        assertThat(hierarchy, hasItem(afterConf));
-
-        boolean foundBeforeConf = false;
-        for (Configuration configuration : hierarchy) {
-            if (configuration.equals(beforeConf)) {
-                foundBeforeConf = true;
-            }
-            if (configuration.equals(afterConf)) {
-                assertThat(foundBeforeConf, equalTo(true));
-            }
-        }
-    }
-
-    @Test
-    public void getAll() {
-        final Configuration conf1 = createNamedConfiguration("testConf1");
-        final Configuration conf2 = createNamedConfiguration("testConf2");
-        context.checking(new Expectations(){{
-            one(configurationContainer).getAll();
-            will(returnValue(toSet(conf1, conf2)));
-        }});
-        assertThat(configuration.getAll(), equalTo(toSet(conf1, conf2)));
-    }
-
-    @Test(expected = GradleException.class)
-    public void getAsPathShouldRethrownFailure() {
-        prepareForResolveWithErrors();
-        configuration.resolve();
-    }
-
-    @Test
-    public void resolve() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        makeResolveReturnFileSet(fileSet);
-        assertThat(configuration.resolve(), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void filesWithDependencies() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        assertThat(configuration.files(context.mock(Dependency.class)), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void fileCollectionWithDependencies() {
-        Dependency dependency1 = createDependency("group1", "name", "version");
-        Dependency dependency2 = createDependency("group2", "name", "version");
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(dependency1);
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency1),
-                equalTo(true));
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency2),
-                equalTo(false));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void filesWithSpec() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        Assert.<Set<File>>assertThat(configuration.files(context.mock(Spec.class)), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void fileCollectionWithSpec() {
-        @SuppressWarnings("unchecked")
-        Spec<Dependency> spec = context.mock(Spec.class);
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(spec);
-        assertThat(fileCollection.getDependencySpec(), sameInstance((Object)spec));
-    }
-
-    @Test
-    public void filesWithClosureSpec() {
-        Closure closure = TestUtil.toClosure("{ dep -> dep.group == 'group1' }");
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        assertThat(configuration.files(closure), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void fileCollectionWithClosureSpec() {
-        Closure closure = TestUtil.toClosure("{ dep -> dep.group == 'group1' }");
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(closure);
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(createDependency("group1", "name", "version")),
-                equalTo(true));
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(createDependency("group2", "name", "version")),
-                equalTo(false));
-    }
-
-    @SuppressWarnings("unchecked")
-    private void prepareForFilesBySpec(final Set<File> fileSet) {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, false);
-        context.checking(new Expectations() {{
-            one(resolvedConfiguration).getFiles(with(any(Spec.class)));
-            will(returnValue(fileSet));
-        }});
-    }
-
-    @Test(expected = GradleException.class)
-    public void resolveShouldRethrowFailure() {
-        prepareForResolveWithErrors();
-        configuration.resolve();
-    }
-
-    private void prepareForResolveWithErrors() {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, true);
-        context.checking(new Expectations(){{
-            one(resolvedConfiguration).rethrowFailure();
-            will(throwException(new GradleException()));
-        }});
-    }
-
-    private void makeResolveReturnFileSet(final Set<File> fileSet) {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        context.checking(new Expectations() {{
-            prepareResolve(resolvedConfiguration, false);
-            allowing(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
-            will(returnValue(fileSet));
-        }});
-    }
-
-    @Test
-    public void resolveSuccessfullyAsResolvedConfiguration() {
-        ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, false);
-        assertThat(configuration.getResolvedConfiguration(), equalTo(resolvedConfiguration));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
-        context.checking(new Expectations() {{
-            ResolverResults result = new ResolverResults();
-            result.resolved(resolvedConfiguration, context.mock(ResolutionResult.class), context.mock(ResolvedProjectConfigurationResults.class));
-            allowing(dependencyResolver).resolve(configuration);
-            will(returnValue(result));
-            allowing(resolvedConfiguration).hasError();
-            will(returnValue(withErrors));
-        }});
-    }
-
-    @Test
-    public void multipleResolvesShouldUseCachedResult() {
-        prepareResolve(context.mock(ResolvedConfiguration.class), true);
-        assertThat(configuration.getResolvedConfiguration(), sameInstance(configuration.getResolvedConfiguration()));
-    }
-
-    @Test
-    public void uploadTaskName() {
-        assertThat(configuration.getUploadTaskName(), equalTo("uploadName"));
-    }
-
-    private DefaultConfiguration createNamedConfiguration(String confName) {
-        return new DefaultConfiguration(confName, confName, configurationContainer,
-                dependencyResolver, listenerManager, metaDataProvider, new DefaultResolutionStrategy(), null);
-    }
-    
-    private DefaultConfiguration createNamedConfiguration(String path, String confName) {
-        return new DefaultConfiguration(path, confName, configurationContainer,
-                dependencyResolver, listenerManager, metaDataProvider, new DefaultResolutionStrategy(), null);
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void buildArtifacts() {
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
-        final Configuration otherConfiguration = context.mock(Configuration.class);
-        final TaskDependency otherArtifactTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
-        final PublishArtifactSet inheritedArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
-        DefaultPublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        artifact.builtBy(artifactTaskMock);
-        configuration.getArtifacts().add(artifact);
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-            will(returnValue(inheritedArtifacts));
-
-            allowing(otherConfiguration).getAllDependencies();
-
-            allowing(otherArtifact).getBuildDependencies();
-            will(returnValue(otherArtifactTaskDependencyMock));
-            
-            allowing(otherArtifactTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(otherConfTaskMock)));
-        }});
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-        assertThat((Set<Task>) configuration.getAllArtifacts().getBuildDependencies().getDependencies(context.mock(Task.class, "caller")),
-                equalTo(toSet(artifactTaskMock, otherConfTaskMock)));
-    }
-
-    @Test
-    public void getAllArtifactFiles() {
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
-        final Configuration otherConfiguration = context.mock(Configuration.class);
-        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
-        final File artifactFile1 = new File("artifact1");
-        final File artifactFile2 = new File("artifact2");
-        final PublishArtifact artifact = context.mock(PublishArtifact.class, "artifact");
-        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
-        final PublishArtifactSet otherArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-            will(returnValue(otherArtifacts));
-
-            allowing(otherConfiguration).getAllDependencies();
-
-            allowing(otherConfiguration).getExtendsFrom();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getArtifacts();
-            will(returnValue(toSet(otherArtifact)));
-
-            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(otherConfTaskMock)));
-
-            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(artifactTaskMock)));
-
-            allowing(artifact).getFile();
-            will(returnValue(artifactFile1));
-
-            allowing(otherArtifact).getFile();
-            will(returnValue(artifactFile2));
-
-            allowing(artifact).getBuildDependencies();
-            will(returnValue(artifactTaskDependencyMock));
-
-            allowing(otherArtifact).getBuildDependencies();
-            will(returnValue(otherConfTaskDependencyMock));
-        }});
-
-        configuration.getArtifacts().add(artifact);
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-
-        FileCollection files = configuration.getAllArtifacts().getFiles();
-        assertThat(files.getFiles(), equalTo(toSet(artifactFile1, artifactFile2)));
-        assertThat(files.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(otherConfTaskMock, artifactTaskMock)));
-    }
-
-    @Test
-    public void buildDependenciesDelegatesToAllSelfResolvingDependencies() {
-        final Task target = context.mock(Task.class, "target");
-        final Task projectDepTaskDummy = context.mock(Task.class, "projectDepTask");
-        final Task fileDepTaskDummy = context.mock(Task.class, "fileDepTask");
-        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
-        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
-
-        context.checking(new Expectations() {{
-            TaskDependency projectTaskDependencyDummy = context.mock(TaskDependency.class, "projectDep");
-            TaskDependency fileTaskDependencyStub = context.mock(TaskDependency.class, "fileDep");
-
-            allowing(projectDependencyStub).getBuildDependencies();
-            will(returnValue(projectTaskDependencyDummy));
-
-            allowing(projectTaskDependencyDummy).getDependencies(target);
-            will(returnValue(toSet(projectDepTaskDummy)));
-
-            allowing(fileCollectionDependencyStub).getBuildDependencies();
-            will(returnValue(fileTaskDependencyStub));
-
-            allowing(fileTaskDependencyStub).getDependencies(target);
-            will(returnValue(toSet(fileDepTaskDummy)));
-        }});
-
-        configuration.getDependencies().add(projectDependencyStub);
-        configuration.getDependencies().add(fileCollectionDependencyStub);
-
-        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(fileDepTaskDummy,
-                projectDepTaskDummy)));
-    }
-
-    @Test
-    public void buildDependenciesDelegatesToInheritedConfigurations() {
-        final Task target = context.mock(Task.class, "target");
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final TaskDependency dependencyTaskDependencyStub = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final Configuration otherConfiguration = context.mock(Configuration.class, "otherConf");
-        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
-        final DependencySet inherited = new DefaultDependencySet("dependencies", toDomainObjectSet(Dependency.class, fileCollectionDependencyStub));
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-
-            allowing(otherConfiguration).getAllDependencies();
-            will(returnValue(inherited));
-
-            allowing(fileCollectionDependencyStub).getBuildDependencies();
-            will(returnValue(dependencyTaskDependencyStub));
-
-            allowing(dependencyTaskDependencyStub).getDependencies(target);
-            will(returnValue(toSet(otherConfTaskMock)));
-        }});
-
-        configuration.extendsFrom(otherConfiguration);
-
-        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(otherConfTaskMock)));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test public void taskDependencyFromProjectDependencyWithoutCommonConfiguration() {
-        // This test exists because a NullPointerException was thrown by
-        // getTaskDependencyFromProjectDependency() if the rootProject
-        // defined a task as the same name as a subproject's task, but did
-        // not define the same configuration.
-        final String configName = configuration.getName();
-        final String taskName = "testit";
-        final Task tdTask = context.mock(Task.class, "tdTask");
-        final Project taskProject = context.mock(Project.class, "taskProject");
-        final Project rootProject = context.mock(Project.class, "rootProject");
-        final Project dependentProject = context.mock(Project.class, "dependentProject");
-        final Task desiredTask = context.mock(Task.class, "desiredTask");
-        final Set<Task> taskSet = toSet(desiredTask);
-        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
-
-        context.checking(new Expectations() {{
-            allowing(tdTask).getProject(); will(returnValue(taskProject));
-            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
-            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
-            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
-            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
-
-            // return null to mock not finding the given configuration
-            allowing(configurationContainer).findByName(configName); will(returnValue(null));
-        }});
-
-        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
-        assertThat(td.getDependencies(tdTask), equalTo(Collections.EMPTY_SET));
-    }
-
-
-    @Test
-    public void getDependencies() {
-        Dependency dependency = context.mock(Dependency.class);
-        configuration.getDependencies().add(dependency);
-        assertThat(configuration.getDependencies(), hasSameItems(toSet(dependency)));
-    }
-
-    @Test
-    public void getTypedDependencies() {
-        ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        configuration.getDependencies().add(context.mock(Dependency.class));
-        configuration.getDependencies().add(projectDependency);
-        assertThat(configuration.getDependencies().withType(ProjectDependency.class), hasSameItems(toSet(projectDependency)));
-    }
-
-    @Test
-    public void getTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.getDependencies().add(context.mock(Dependency.class));
-        assertThat(configuration.getDependencies().withType(ProjectDependency.class), isEmpty());
-    }
-
-    @Test
-    public void getAllDependencies() {
-        Dependency dependencyConf = createDependency("group1", "name1", "version1");
-        Dependency dependencyOtherConf1 = createDependency("group1", "name1", "version1");
-        Dependency dependencyOtherConf2 = context.mock(Dependency.class, "dep2");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.getDependencies().add(dependencyConf);
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(dependencyOtherConf1);
-        otherConf.getDependencies().add(dependencyOtherConf2);
-
-        assertThat(configuration.getAllDependencies(), hasSameItems(toLinkedSet(dependencyConf, dependencyOtherConf2)));
-        assertCorrectInstanceInAllDependencies(configuration.getAllDependencies(), dependencyConf);
-    }
-
-    @Test
-    public void getAllTypedDependencies() {
-        ProjectDependency projectDependencyCurrentConf = context.mock(ProjectDependency.class, "projectDepCurrentConf");
-        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
-        configuration.getDependencies().add(projectDependencyCurrentConf);
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.extendsFrom(otherConf);
-        ProjectDependency projectDependencyExtendedConf = context.mock(ProjectDependency.class, "projectDepExtendedConf");
-        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
-        otherConf.getDependencies().add(projectDependencyExtendedConf);
-
-        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), hasSameItems(toLinkedSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
-    }
-
-    @Test
-    public void getAllTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
-
-        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), isEmpty());
-    }
-
-    @Test
-    public void getAllArtifacts() {
-        PublishArtifact artifactConf = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        PublishArtifact artifactOtherConf2 = createPublishArtifact("name2", "ext2", "type2", "classifier2");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.getArtifacts().add(artifactConf);
-        configuration.extendsFrom(otherConf);
-        otherConf.getArtifacts().add(artifactOtherConf2);
-        assertThat(configuration.getAllArtifacts(), hasSameItems(toLinkedSet(artifactConf, artifactOtherConf2)));
-    }
-
-    @Test
-    public void artifactAddedAction() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-
-        context.checking(new Expectations() {{
-            one(closure).call(artifact);
-        }});
-
-        configuration.getArtifacts().whenObjectAdded(TestUtil.toClosure(closure));
-        configuration.getArtifacts().add(artifact);
-    }
-
-    @Test
-    public void artifactRemovedAction() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-
-        configuration.getArtifacts().add(artifact);
-
-        context.checking(new Expectations() {{
-            one(closure).call(artifact);
-        }});
-
-        configuration.getArtifacts().whenObjectRemoved(TestUtil.toClosure(closure));
-
-        configuration.getArtifacts().remove(artifact);
-    }
-
-    @Test
-    public void removeArtifact() {
-        PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.getArtifacts().add(artifact);
-        configuration.getArtifacts().remove(artifact);
-        assertThat(configuration.getAllArtifacts(), Matchers.isEmpty());
-    }
-
-    @Test
-    public void removeArtifactWithUnknownArtifact() {
-        PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.getArtifacts().add(artifact);
-        configuration.getArtifacts().remove(createPublishArtifact("name2", "ext1", "type1", "classifier1"));
-        assertThat(configuration.getAllArtifacts(), hasSameItems(toSet(artifact)));
-    }
-
-    private void assertCorrectInstanceInAllDependencies(Set<Dependency> allDependencies, Dependency correctInstance) {
-        for (Dependency dependency : allDependencies) {
-            if (dependency == correctInstance) {
-                return;
-            }
-        }
-        fail("Correct instance is missing!");
-    }
-
-    @Test
-    public void copy() {
-        prepareConfigurationForCopyTest();
-
-        Configuration copiedConfiguration = configuration.copy();
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getDependencies());
-    }
-
-    @Test
-    public void copyWithSpec() {
-        prepareConfigurationForCopyTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
-
-        Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
-            public boolean isSatisfiedBy(Dependency element) {
-                return !element.getGroup().equals("group3");
-            }
-        });
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    @Test
-    public void copyWithClosure() {
-        prepareConfigurationForCopyTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
-
-        Closure specClosure = TestUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
-        Configuration copiedConfiguration = configuration.copy(specClosure);
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    private void prepareConfigurationForCopyTest() {
-        configuration.setVisible(false);
-        configuration.setTransitive(false);
-        configuration.setDescription("descript");
-        configuration.exclude(toMap("group", "value"));
-        configuration.exclude(toMap("group", "value2"));
-        configuration.getArtifacts().add(createPublishArtifact("name1", "ext1", "type1", "classifier1"));
-        configuration.getArtifacts().add(createPublishArtifact("name2", "ext2", "type2", "classifier2"));
-        configuration.getDependencies().add(createDependency("group1", "name1", "version1"));
-        configuration.getDependencies().add(createDependency("group2", "name2", "version2"));
-    }
-
-    private void assertThatCopiedConfigurationHasElementsAndName(Configuration copiedConfiguration, Set<Dependency> expectedDependencies) {
-        assertThat(copiedConfiguration.getName(), equalTo(configuration.getName() + "Copy"));
-        assertThat(copiedConfiguration.isVisible(), equalTo(configuration.isVisible()));
-        assertThat(copiedConfiguration.isTransitive(), equalTo(configuration.isTransitive()));
-        assertThat(copiedConfiguration.getDescription(), equalTo(configuration.getDescription()));
-        assertThat(asSet(copiedConfiguration.getAllArtifacts()), equalTo(asSet(configuration.getAllArtifacts())));
-        assertThat(copiedConfiguration.getExcludeRules(), equalTo(configuration.getExcludeRules()));
-        assertThat(copiedConfiguration.getExcludeRules().iterator().next(), not(sameInstance(configuration.getExcludeRules().iterator().next())));
-        assertThat(WrapUtil.asSet(copiedConfiguration.getDependencies()), equalTo(WrapUtil.asSet(expectedDependencies)));
-        assertNotSameInstances(copiedConfiguration.getDependencies(), expectedDependencies);
-    }
-
-    @Test
-    public void copyRecursive() {
-        prepareConfigurationForCopyRecursiveTest();
-
-        Configuration copiedConfiguration = configuration.copyRecursive();
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getAllDependencies());
-    }
-
-    @Test
-    public void copyRecursiveWithSpec() {
-        prepareConfigurationForCopyRecursiveTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
-
-        Closure specClosure = TestUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
-        Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    @Test
-    public void copyRecursiveWithClosure() {
-        prepareConfigurationForCopyRecursiveTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
-
-        Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
-            public boolean isSatisfiedBy(Dependency element) {
-                return !element.getGroup().equals("group3");
-            }
-        });
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    private void prepareConfigurationForCopyRecursiveTest() {
-        prepareConfigurationForCopyTest();
-        Dependency similarDependency2InOtherConf = createDependency("group2", "name2", "version2");
-        Dependency otherConfDependency = createDependency("group4", "name4", "version4");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        otherConf.getDependencies().add(similarDependency2InOtherConf);
-        otherConf.getDependencies().add(otherConfDependency);
-        configuration.extendsFrom(otherConf);
-    }
-
-    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
-        for (Dependency dependency : dependencies) {
-            assertHasEqualButNotSameInstance(dependency, otherDependencies);
-        }
-    }
-
-    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
-        assertThat(otherDependencies, hasItem(dependency));
-        for (Dependency otherDependency : otherDependencies) {
-            if (otherDependency.equals(dependency)) {
-                assertThat(otherDependency, not(sameInstance(dependency)));
-            }
-        }
-    }
-
-    @Test
-    public void propertyChangeWithNonUnresolvedStateShouldThrowEx() {
-        makeResolveReturnFileSet(new HashSet<File>());
-        configuration.resolve();
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setTransitive(true);
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setExcludeRules(new HashSet<ExcludeRule>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setExtendsFrom(new HashSet<Configuration>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setVisible(true);
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getDependencies().add(context.mock(Dependency.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getDependencies().add(context.mock(Dependency.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.exclude(new HashMap<String, String>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.extendsFrom(context.mock(Configuration.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().add(context.mock(PublishArtifact.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().remove(context.mock(PublishArtifact.class, "removeArtifact"));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().add(context.mock(PublishArtifact.class, "removeArtifact"));
-            }
-        });
-    }
-    
-    @Test
-    public void dumpString() {
-        Dependency configurationDependency = createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfSimilarDependency = createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfDependency = createDependency("dumpgroup2", "dumpname2", "dumpversion2");
-        Configuration otherConf = createNamedConfiguration("dumpConf");
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(otherConfDependency);
-        otherConf.getDependencies().add(otherConfSimilarDependency);
-        configuration.getDependencies().add(configurationDependency);
-
-        assertThat(configuration.dump(),
-                containsString(
-                "\nConfiguration:"
-                + "  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration'"
-                + "  name='name'"
-                + "  hashcode='"+ configuration.hashCode() +"'"
-                + "\nLocal Dependencies:"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
-                + "\nLocal Artifacts:"
-                + "\n   none"
-                + "\nAll Dependencies:"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup2', name='dumpname2', version='dumpversion2', configuration='default'}"
-                + "\nAll Artifacts:"
-                + "\n   none"));
-    }
-
-    ModuleDependency createDependency(String group, String name, String version) {
-        return new DefaultExternalModuleDependency(group, name, version);
-    }
-
-    DefaultPublishArtifact createPublishArtifact(String name, String extension, String type, String classifier) {
-        return new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(""));
-    }
-
-    private void assertInvalidUserDataException(Executer executer) {
-        try {
-            executer.execute();
-            fail();
-        } catch (InvalidUserDataException e) {
-            // ignore
-        }
-    }
-
-    private static interface Executer {
-        void execute();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
deleted file mode 100644
index 7a82a34..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
+++ /dev/null
@@ -1,154 +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.artifacts.ModuleVersionSelector
-import org.gradle.api.artifacts.component.ModuleComponentSelector
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
-import spock.lang.Specification
-
-class DefaultDependencyResolveDetailsSpec extends Specification {
-
-    def "can specify version to use"() {
-        when:
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "1.0")
-        !details.updated
-        !details.selectionReason
-
-        when:
-        details.useVersion("1.0") //the same version
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "1.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-
-        when:
-        details.useVersion("2.0") //different version
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "2.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "can specify version with selection reason"() {
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        when:
-        details.useVersion("1.0", VersionSelectionReasons.FORCED) //same version
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "1.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.FORCED
-
-        when:
-        details.useVersion("3.0", VersionSelectionReasons.FORCED) //different version
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "3.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.FORCED
-    }
-
-    def "can override version and selection reason"() {
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        when:
-        details.useVersion("2.0", VersionSelectionReasons.FORCED)
-        details.useVersion("3.0", VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        details.requested == newVersionSelector("org", "foo", "1.0")
-        details.target == newVersionSelector("org", "foo", "3.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "does not allow null version"() {
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        when:
-        details.useVersion(null)
-
-        then:
-        thrown(IllegalArgumentException)
-
-        when:
-        details.useVersion(null, VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        thrown(IllegalArgumentException)
-    }
-
-    def "can specify target module"() {
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        when:
-        details.useTarget("org:bar:2.0")
-
-        then:
-        details.target.toString() == 'org:bar:2.0'
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "can mix configuring version and target module"() {
-        def details = newDependencyResolveDetails("org", "foo", "1.0")
-
-        when:
-        details.useVersion("1.5")
-
-        then:
-        details.target.toString() == 'org:foo:1.5'
-
-        when:
-        details.useTarget("com:bar:3.0")
-
-        then:
-        details.target.toString() == 'com:bar:3.0'
-
-        when:
-        details.useVersion('5.0')
-
-        then:
-        details.target.toString() == 'com:bar:5.0'
-    }
-
-    private static def newDependencyResolveDetails(String group, String name, String version) {
-        return new DefaultDependencyResolveDetails(new DefaultModuleDependencySubstitution(newComponentSelector(group, name, version), newVersionSelector(group, name, version)))
-    }
-
-    private static ModuleComponentSelector newComponentSelector(String group, String module, String version) {
-        return DefaultModuleComponentSelector.newSelector(group, module, version)
-    }
-
-    private static ModuleVersionSelector newVersionSelector(String group, String name, String version) {
-        return DefaultModuleVersionSelector.newSelector(group, name, version)
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitutionTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitutionTest.groovy
deleted file mode 100644
index caac01a..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultModuleDependencySubstitutionTest.groovy
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.Project
-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.ivyservice.resolveengine.result.VersionSelectionReasons
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
-import org.gradle.internal.typeconversion.UnsupportedNotationException
-import spock.lang.Specification
-
-class DefaultModuleDependencySubstitutionTest extends Specification {
-
-    def "can specify version to use"() {
-        when:
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target == newComponentSelector("org", "foo", "1.0")
-        !details.updated
-        !details.selectionReason
-
-        when:
-        details.useVersion("1.0") //the same version
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target == newComponentSelector("org", "foo", "1.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-
-        when:
-        details.useVersion("2.0") //different version
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target != newComponentSelector("org", "foo", "1.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-
-        details.target.version == "2.0"
-        details.target.module == newComponentSelector("org", "foo", "1.0").module
-        details.target.group == newComponentSelector("org", "foo", "1.0").group
-    }
-
-    def "can specify version with selection reason"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useVersion("1.0", VersionSelectionReasons.FORCED) //same version
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target == newComponentSelector("org", "foo", "1.0")
-        details.updated
-        details.selectionReason == VersionSelectionReasons.FORCED
-
-        when:
-        details.useVersion("3.0", VersionSelectionReasons.FORCED) //different version
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target.version == "3.0"
-        details.target.module == newComponentSelector("org", "foo", "1.0").module
-        details.target.group == newComponentSelector("org", "foo", "1.0").group
-        details.updated
-        details.selectionReason == VersionSelectionReasons.FORCED
-    }
-
-    def "can override version and selection reason"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useVersion("2.0", VersionSelectionReasons.FORCED)
-        details.useVersion("3.0", VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        details.requested == newComponentSelector("org", "foo", "1.0")
-        details.target.version == "3.0"
-        details.target.module == newComponentSelector("org", "foo", "1.0").module
-        details.target.group == newComponentSelector("org", "foo", "1.0").group
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "does not allow null target"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useTarget(null)
-
-        then:
-        thrown(UnsupportedNotationException)
-
-        when:
-        details.useTarget(null, VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        thrown(UnsupportedNotationException)
-    }
-
-    def "does not allow null version"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useVersion(null)
-
-        then:
-        thrown(IllegalArgumentException)
-
-        when:
-        details.useVersion(null, VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        thrown(IllegalArgumentException)
-    }
-
-    def "can specify target module"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useTarget("org:bar:2.0")
-
-        then:
-        details.target instanceof ModuleComponentSelector
-        details.target.toString() == 'org:bar:2.0'
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "can specify target project"() {
-        def project = Mock(Project)
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useTarget(project)
-
-        then:
-        _ * project.path >> ":bar"
-        details.target instanceof ProjectComponentSelector
-        details.target.projectPath == ":bar"
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "can mix configuring version and target module"() {
-        def details = newModuleDependencySubstitution("org", "foo", "1.0")
-
-        when:
-        details.useVersion("1.5")
-
-        then:
-        details.target.toString() == 'org:foo:1.5'
-
-        when:
-        details.useTarget("com:bar:3.0")
-
-        then:
-        details.target.toString() == 'com:bar:3.0'
-
-        when:
-        details.useVersion('2.0')
-
-        then:
-        details.target.toString() == 'org:foo:2.0'
-    }
-
-    private static def newModuleDependencySubstitution(String group, String name, String version) {
-        return new DefaultModuleDependencySubstitution(newComponentSelector(group, name, version), DefaultModuleVersionSelector.newSelector(group, name, version))
-    }
-
-    private static ModuleComponentSelector newComponentSelector(String group, String module, String version) {
-        return DefaultModuleComponentSelector.newSelector(group, module, version)
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitutionTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitutionTest.groovy
deleted file mode 100644
index 84a5af5..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultProjectDependencySubstitutionTest.groovy
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.Project
-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.ivyservice.resolveengine.result.VersionSelectionReasons
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
-import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
-import org.gradle.internal.typeconversion.UnsupportedNotationException
-import spock.lang.Specification
-
-class DefaultProjectDependencySubstitutionTest extends Specification {
-
-    def "can override target and selection reason"() {
-        def details = newProjectDependencySubstitution("foo")
-
-        when:
-        details.useTarget("org:foo:2.0", VersionSelectionReasons.FORCED)
-        details.useTarget("org:foo:3.0", VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        details.requested == newComponentSelector(":foo")
-        details.target.version == "3.0"
-        details.target.module == newComponentSelector("org", "foo", "1.0").module
-        details.target.group == newComponentSelector("org", "foo", "1.0").group
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "does not allow null target"() {
-        def details = newProjectDependencySubstitution("foo")
-
-        when:
-        details.useTarget(null)
-
-        then:
-        thrown(UnsupportedNotationException)
-
-        when:
-        details.useTarget(null, VersionSelectionReasons.SELECTED_BY_RULE)
-
-        then:
-        thrown(UnsupportedNotationException)
-    }
-
-    def "can specify target module"() {
-        def details = newProjectDependencySubstitution("foo")
-
-        when:
-        details.useTarget("org:bar:2.0")
-
-        then:
-        details.target instanceof ModuleComponentSelector
-        details.target.toString() == 'org:bar:2.0'
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    def "can specify target project"() {
-        def project = Mock(Project)
-        def details = newProjectDependencySubstitution("foo")
-
-        when:
-        details.useTarget(project)
-
-        then:
-        _ * project.path >> ":bar"
-        details.target instanceof ProjectComponentSelector
-        details.target.projectPath == ":bar"
-        details.updated
-        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
-    }
-
-    private static def newProjectDependencySubstitution(String name) {
-        return new DefaultProjectDependencySubstitution(newComponentSelector(":" + name), DefaultModuleVersionSelector.newSelector("com.test", name, "1.0"))
-    }
-
-    private static ProjectComponentSelector newComponentSelector(String projectPath) {
-        return DefaultProjectComponentSelector.newSelector(projectPath)
-    }
-
-    private static ModuleComponentSelector newComponentSelector(String group, String module, String version) {
-        return DefaultModuleComponentSelector.newSelector(group, module, version)
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolverSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolverSpec.groovy
deleted file mode 100644
index 236a0d3..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencySubstitutionResolverSpec.groovy
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice
-
-import org.gradle.api.Action
-import org.gradle.api.artifacts.DependencySubstitution
-import org.gradle.api.artifacts.ModuleDependencySubstitution
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
-import org.gradle.internal.component.model.DependencyMetaData
-import org.gradle.internal.resolve.ModuleVersionResolveException
-import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver
-import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult
-import spock.lang.Specification
-
-class DependencySubstitutionResolverSpec extends Specification {
-    def requested = new DefaultModuleVersionSelector("group", "module", "version")
-    def selector = new DefaultModuleComponentSelector("group", "module", "version")
-    def dependency = Mock(DependencyMetaData) {
-        getRequested() >> requested
-        getSelector() >> selector
-    }
-    def result = Mock(BuildableComponentIdResolveResult)
-    def target = Mock(DependencyToComponentIdResolver)
-    def rule = Mock(Action)
-    def resolver = new DependencySubstitutionResolver(target, rule)
-
-    def "passes through dependency when it does not match any rule"() {
-        given:
-        rule.execute(_) >> { DependencySubstitution details ->
-        }
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * target.resolve(dependency, result)
-    }
-
-    def "replaces dependency by rule"() {
-        def substitutedDependency = Stub(DependencyMetaData)
-
-        given:
-        rule.execute(_) >> { ModuleDependencySubstitution details ->
-            details.useVersion("new")
-        }
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * dependency.withTarget(DefaultModuleComponentSelector.newSelector("group", "module", "new")) >> substitutedDependency
-        1 * target.resolve(substitutedDependency, result)
-    }
-
-    def "explosive rule yields failure result that provides context"() {
-        given:
-        def failure = new RuntimeException("broken")
-        rule.execute(_) >> { DependencySubstitution details ->
-            throw failure
-        }
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * result.failed(_) >> { ModuleVersionResolveException e ->
-            assert e.cause == failure
-        }
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
index a26a83f..8c2af8b 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
@@ -80,10 +80,12 @@ class ErrorHandlingArtifactDependencyResolverTest extends Specification {
         resolvedConfiguration.getResolvedArtifacts() >> { throw failure }
         resolvedConfiguration.getLenientConfiguration() >> { throw failure }
 
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, resolutionResult, projectConfigResult) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolutionResult, projectConfigResult) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def result = results.resolvedConfiguration
@@ -107,10 +109,12 @@ class ErrorHandlingArtifactDependencyResolverTest extends Specification {
         lenientConfiguration.getArtifacts(_) >> { throw failure }
         lenientConfiguration.getUnresolvedModuleDependencies() >> { throw failure }
 
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, resolutionResult, projectConfigResult) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolutionResult, projectConfigResult) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def result = results.resolvedConfiguration.lenientConfiguration
@@ -127,10 +131,12 @@ class ErrorHandlingArtifactDependencyResolverTest extends Specification {
 
         resolutionResult.root >> { throw failure }
 
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, resolutionResult, projectConfigResult) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolutionResult, projectConfigResult) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def result = results.resolutionResult
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
index 8abfc6a..1c71685 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
@@ -46,12 +46,14 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "returns correct resolved configuration"() {
         given:
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -63,12 +65,14 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "uses configuration transitive setting"() {
         given:
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> false
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -77,12 +81,14 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "delegates to provided resolved configuration"() {
         given:
-        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(resolvedConfiguration, Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolve(configuration, repositories, metadataHandler, results) >> { results.resolved(Mock(ResolutionResult), Mock(ResolvedProjectConfigurationResults)) }
+        delegate.resolveArtifacts(configuration, repositories, metadataHandler, results) >> { results.withResolvedConfiguration(resolvedConfiguration) }
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
         resolver.resolve(configuration, repositories, metadataHandler, results)
+        resolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
         results.resolvedConfiguration.getFirstLevelModuleDependencies(Specs.satisfyAll())
         results.resolvedConfiguration.getResolvedArtifacts()
         results.resolvedConfiguration.hasError()
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
index 3a1833f..52a04ff 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
@@ -44,6 +44,7 @@ class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specificati
 
         when:
         dependencyResolver.resolve(configuration, repositories, metadataHandler, results)
+        dependencyResolver.resolveArtifacts(configuration, repositories, metadataHandler, results)
 
         then:
         def resolvedConfig = results.resolvedConfiguration
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
index 515373f..cbb74ba 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.clientmodule
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.gradle.api.artifacts.ClientModule
 import org.gradle.api.artifacts.ModuleDependency
 import org.gradle.api.artifacts.component.ComponentIdentifier
@@ -24,6 +23,7 @@ import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
 import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData
 import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaData
+import org.gradle.internal.component.model.ComponentOverrideMetadata
 import org.gradle.internal.component.model.DependencyMetaData
 import org.gradle.internal.resolve.ModuleVersionResolveException
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver
@@ -40,28 +40,27 @@ class ClientModuleResolverTest extends Specification {
     def id = Mock(ComponentIdentifier)
     def result = Mock(BuildableComponentResolveResult)
     def metaData = Mock(MutableModuleComponentResolveMetaData)
+    def componentRequestMetaData = Mock(ComponentOverrideMetadata)
     def dependency = Mock(DslOriginDependencyMetaData)
 
     def "replaces meta-data for a client module dependency"() {
         def clientModule = Mock(ClientModule)
         def dep = Mock(ModuleDependency)
-        def moduleDescriptor = Mock(ModuleDescriptor)
         def dependencyMetaData = Mock(DependencyMetaData)
         def artifact = Mock(ModuleComponentArtifactMetaData)
 
         when:
-        resolver.resolve(dependency, id, result)
+        resolver.resolve(id, componentRequestMetaData, result)
 
         then:
-        1 * target.resolve(dependency, id, result)
+        1 * target.resolve(id, componentRequestMetaData, result)
         1 * result.getFailure() >> null
-        1 * dependency.source >> clientModule
+        1 * componentRequestMetaData.clientModule >> clientModule
         1 * result.getMetaData() >> metaData
         1 * metaData.copy() >> metaData
         1 * clientModule.getDependencies() >> ([dep] as Set)
         1 * dep.getConfiguration() >> "config"
-        1 * metaData.getDescriptor() >> moduleDescriptor
-        1 * dependencyDescriptorFactory.createDependencyDescriptor("config", moduleDescriptor, dep) >> dependencyMetaData
+        1 * dependencyDescriptorFactory.createDependencyDescriptor("config", dep) >> dependencyMetaData
         1 * metaData.setDependencies([dependencyMetaData])
         1 * metaData.artifact('jar', 'jar', null) >> artifact
         1 * metaData.setArtifacts({
@@ -72,24 +71,22 @@ class ClientModuleResolverTest extends Specification {
     }
 
     def "does not replace meta-data when not client module"() {
-        def moduleDependency = Mock(ModuleDependency)
-
         when:
-        resolver.resolve(dependency, id, result)
+        resolver.resolve(id, componentRequestMetaData, result)
 
         then:
-        1 * target.resolve(dependency, id, result)
+        1 * target.resolve(id, componentRequestMetaData, result)
         1 * result.getFailure() >> null
-        1 * dependency.source >> moduleDependency
+        1 * componentRequestMetaData.clientModule >> null
         0 * _
     }
 
     def "does not replace meta-data for broken module version"() {
         when:
-        resolver.resolve(dependency, id, result)
+        resolver.resolve(id, componentRequestMetaData, result)
 
         then:
-        1 * target.resolve(dependency, id, result)
+        1 * target.resolve(id, componentRequestMetaData, result)
         _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
         0 * _
     }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetailsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetailsSpec.groovy
new file mode 100644
index 0000000..594983d
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencyResolveDetailsSpec.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
+import spock.lang.Specification
+
+class DefaultDependencyResolveDetailsSpec extends Specification {
+
+    def "can specify version to use"() {
+        when:
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "1.0")
+        !details.updated
+        !details.selectionReason
+
+        when:
+        details.useVersion("1.0") //the same version
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "1.0")
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+
+        when:
+        details.useVersion("2.0") //different version
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "2.0")
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "can specify version with selection reason"() {
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        when:
+        details.useVersion("1.0", VersionSelectionReasons.FORCED) //same version
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "1.0")
+        details.updated
+        details.selectionReason == VersionSelectionReasons.FORCED
+
+        when:
+        details.useVersion("3.0", VersionSelectionReasons.FORCED) //different version
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "3.0")
+        details.updated
+        details.selectionReason == VersionSelectionReasons.FORCED
+    }
+
+    def "can override version and selection reason"() {
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        when:
+        details.useVersion("2.0", VersionSelectionReasons.FORCED)
+        details.useVersion("3.0", VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        details.requested == newVersionSelector("org", "foo", "1.0")
+        details.target == newVersionSelector("org", "foo", "3.0")
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "does not allow null version"() {
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        when:
+        details.useVersion(null)
+
+        then:
+        thrown(IllegalArgumentException)
+
+        when:
+        details.useVersion(null, VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+
+    def "can specify target module"() {
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        when:
+        details.useTarget("org:bar:2.0")
+
+        then:
+        details.target.toString() == 'org:bar:2.0'
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "can mix configuring version and target module"() {
+        def details = newDependencyResolveDetails("org", "foo", "1.0")
+
+        when:
+        details.useVersion("1.5")
+
+        then:
+        details.target.toString() == 'org:foo:1.5'
+
+        when:
+        details.useTarget("com:bar:3.0")
+
+        then:
+        details.target.toString() == 'com:bar:3.0'
+
+        when:
+        details.useVersion('5.0')
+
+        then:
+        details.target.toString() == 'com:bar:5.0'
+    }
+
+    private static def newDependencyResolveDetails(String group, String name, String version) {
+        return new DefaultDependencyResolveDetails(new DefaultDependencySubstitution(newComponentSelector(group, name, version), newVersionSelector(group, name, version)))
+    }
+
+    private static ModuleComponentSelector newComponentSelector(String group, String module, String version) {
+        return DefaultModuleComponentSelector.newSelector(group, module, version)
+    }
+
+    private static ModuleVersionSelector newVersionSelector(String group, String name, String version) {
+        return DefaultModuleVersionSelector.newSelector(group, name, version)
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionSpec.groovy
new file mode 100644
index 0000000..7d9eb27
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionSpec.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution
+import org.gradle.api.Project
+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.component.ProjectComponentSelector
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.internal.typeconversion.UnsupportedNotationException
+import spock.lang.Specification
+
+class DefaultDependencySubstitutionSpec extends Specification {
+    def componentSelector = Mock(ComponentSelector)
+    def moduleVersionSelector = Mock(ModuleVersionSelector)
+    def details = new DefaultDependencySubstitution(componentSelector, moduleVersionSelector)
+
+    def "can override target and selection reason for project"() {
+        when:
+        details.useTarget("org:foo:2.0", VersionSelectionReasons.FORCED)
+        details.useTarget("org:foo:3.0", VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        details.requested == componentSelector
+        details.oldRequested == moduleVersionSelector
+        details.target.group == "org"
+        details.target.module == "foo"
+        details.target.version == "3.0"
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "does not allow null target"() {
+        when:
+        details.useTarget(null)
+
+        then:
+        thrown(UnsupportedNotationException)
+
+        when:
+        details.useTarget(null, VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+
+    def "can specify target module"() {
+        when:
+        details.useTarget("org:bar:2.0")
+
+        then:
+        details.target instanceof ModuleComponentSelector
+        details.target.toString() == 'org:bar:2.0'
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "can specify target project"() {
+        def project = Mock(Project)
+
+        when:
+        details.useTarget(project)
+
+        then:
+        _ * project.path >> ":bar"
+        details.target instanceof ProjectComponentSelector
+        details.target.projectPath == ":bar"
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy
new file mode 100644
index 0000000..0874ed5
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal
+import org.gradle.api.internal.artifacts.configurations.MutationValidator
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.api.internal.artifacts.configurations.MutationValidator.MutationType.STRATEGY
+
+class DefaultDependencySubstitutionsSpec extends Specification {
+    DependencySubstitutionsInternal substitutions;
+
+    def setup() {
+        substitutions = new DefaultDependencySubstitutions()
+    }
+
+    def "provides no op resolve rule when no rules or forced modules configured"() {
+        given:
+        def details = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(details)
+
+        then:
+        0 * details._
+    }
+
+    def "all() matches modules and projects"() {
+        given:
+        def action = Mock(Action)
+        substitutions.all(action)
+
+        def moduleDetails = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(moduleDetails)
+
+        then:
+        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
+        1 * action.execute(moduleDetails)
+        0 * _
+
+        def projectDetails = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(projectDetails)
+
+        then:
+        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
+        1 * action.execute(projectDetails)
+        0 * _
+    }
+
+    def "allWithDependencyResolveDetails() wraps substitution in legacy format"() {
+        given:
+        def action = Mock(Action)
+        substitutions.allWithDependencyResolveDetails(action)
+
+        def moduleOldRequested = DefaultModuleVersionSelector.newSelector("org.utils", "api", "1.5")
+        def moduleTarget = DefaultModuleComponentSelector.newSelector(moduleOldRequested)
+        def moduleDetails = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(moduleDetails)
+
+        then:
+        _ * moduleDetails.target >> moduleTarget
+        _ * moduleDetails.oldRequested >> moduleOldRequested
+        1 * action.execute({ DependencyResolveDetailsInternal details ->
+            details.requested == moduleOldRequested
+        })
+        0 * _
+
+        def projectOldRequested = DefaultModuleVersionSelector.newSelector("org.utils", "api", "1.5")
+        def projectTarget = DefaultProjectComponentSelector.newSelector(":api")
+        def projectDetails = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(projectDetails)
+
+        then:
+        _ * projectDetails.target >> projectTarget
+        _ * projectDetails.oldRequested >> projectOldRequested
+        1 * action.execute({ DependencyResolveDetailsInternal details ->
+            details.requested == projectOldRequested
+        })
+        0 * _
+    }
+
+    @Unroll
+    def "substitute module() matches only given module: #matchingModule"() {
+        given:
+        def matchingSubstitute = Mock(ComponentSelector)
+        def nonMatchingSubstitute = Mock(ComponentSelector)
+        def moduleDetails = Mock(DependencySubstitutionInternal)
+
+        with(substitutions) {
+            substitute module(matchingModule) with matchingSubstitute
+            substitute module(nonMatchingModule) with nonMatchingSubstitute
+        }
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(moduleDetails)
+
+        then:
+        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
+        1 * moduleDetails.useTarget(matchingSubstitute)
+        0 * _
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(moduleDetails)
+
+        then:
+        _ * moduleDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
+        0 * _
+
+        where:
+        matchingModule      | nonMatchingModule
+        "org.utils:api:1.5" | "org.utils:api:1.6"
+        "org.utils:api"     | "org.utils:impl"
+    }
+
+    def "cannot substitute with unversioned module selector"() {
+        when:
+        with(substitutions) {
+            substitute project("foo") with module('group:name')
+        }
+
+        then:
+        def t = thrown(InvalidUserDataException)
+        t.message == "Must specify version for target of dependency substitution"
+    }
+
+    @Unroll
+    def "substitute project() matches only given project: #matchingProject"() {
+        given:
+        def matchingSubstitute = Mock(ComponentSelector)
+        def nonMatchingSubstitute = Mock(ComponentSelector)
+
+        with(substitutions) {
+            substitute project(matchingProject) with matchingSubstitute
+            substitute project(nonMatchingProject) with nonMatchingSubstitute
+        }
+
+        def projectDetails = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(projectDetails)
+
+        then:
+        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
+        1 * projectDetails.useTarget(matchingSubstitute)
+        0 * _
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(projectDetails)
+
+        then:
+        _ * projectDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
+        0 * _
+
+        where:
+        matchingProject | nonMatchingProject
+        ":api"          | ":impl"
+    }
+
+    def "provides dependency substitution rule that orderly aggregates user specified rules"() {
+        given:
+        substitutions.all({ it.useTarget("1.0") } as Action)
+        substitutions.all({ it.useTarget("2.0") } as Action)
+        substitutions.all({ it.useTarget("3.0") } as Action)
+        def details = Mock(DependencySubstitutionInternal)
+
+        when:
+        substitutions.dependencySubstitutionRule.execute(details)
+
+        then:
+        1 * details.useTarget("1.0")
+        then:
+        1 * details.useTarget("2.0")
+        then:
+        1 * details.useTarget("3.0")
+        0 * details._
+    }
+
+    def "mutations trigger lenient validation"() {
+        given:
+        def validator = Mock(MutationValidator)
+        substitutions.setMutationValidator(validator)
+
+        when:
+        substitutions.all(Mock(Action))
+        then:
+        1 * validator.validateMutation(STRATEGY)
+
+        when:
+        with(substitutions) {
+            substitute module("org:foo") with project(":bar")
+        }
+        then:
+        1 * validator.validateMutation(STRATEGY)
+
+        when:
+        with(substitutions) {
+            substitute project(":bar") with module("org:foo:1.0")
+        }
+        then:
+        1 * validator.validateMutation(STRATEGY)
+    }
+
+    def "mutating copy does not trigger original validator"() {
+        given:
+        def validator = Mock(MutationValidator)
+        substitutions.setMutationValidator(validator)
+        def copy = substitutions.copy()
+
+        when:
+        copy.all(Mock(Action))
+
+        then:
+        0 * validator.validateMutation(_)
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolverSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolverSpec.groovy
new file mode 100644
index 0000000..c8e00c1
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DependencySubstitutionResolverSpec.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution
+import org.gradle.api.Action
+import org.gradle.api.artifacts.DependencySubstitution
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
+import org.gradle.internal.component.model.DependencyMetaData
+import org.gradle.internal.resolve.ModuleVersionResolveException
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver
+import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult
+import spock.lang.Specification
+
+class DependencySubstitutionResolverSpec extends Specification {
+    def requested = new DefaultModuleVersionSelector("group", "module", "version")
+    def selector = new DefaultModuleComponentSelector("group", "module", "version")
+    def dependency = Mock(DependencyMetaData) {
+        getRequested() >> requested
+        getSelector() >> selector
+    }
+    def result = Mock(BuildableComponentIdResolveResult)
+    def target = Mock(DependencyToComponentIdResolver)
+    def rule = Mock(Action)
+    def resolver = new DependencySubstitutionResolver(target, rule)
+
+    def "passes through dependency when it does not match any rule"() {
+        given:
+        rule.execute(_) >> { DependencySubstitution details ->
+        }
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * target.resolve(dependency, result)
+    }
+
+    def "replaces dependency by rule"() {
+        def substitutedDependency = Stub(DependencyMetaData)
+
+        given:
+        rule.execute(_) >> { DependencySubstitutionInternal details ->
+            details.useTarget("group:module:new")
+        }
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * dependency.withTarget(DefaultModuleComponentSelector.newSelector("group", "module", "new")) >> substitutedDependency
+        1 * target.resolve(substitutedDependency, result)
+    }
+
+    def "explosive rule yields failure result that provides context"() {
+        given:
+        def failure = new RuntimeException("broken")
+        rule.execute(_) >> { DependencySubstitution details ->
+            throw failure
+        }
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * result.failed(_) >> { ModuleVersionResolveException e ->
+            assert e.cause == failure
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverterTest.groovy
new file mode 100644
index 0000000..9fdee27
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/ModuleSelectorStringNotationConverterTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution
+import org.gradle.api.artifacts.component.ComponentSelector
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
+import org.gradle.internal.typeconversion.NotationParserBuilder
+import org.gradle.internal.typeconversion.UnsupportedNotationException
+import spock.lang.Specification
+import spock.lang.Subject
+
+class ModuleSelectorStringNotationConverterTest extends Specification {
+
+    @Subject parser = NotationParserBuilder.toType(ComponentSelector).converter(new ModuleSelectorStringNotationConverter()).toComposite()
+
+    def "parses module identifier notation"() {
+        expect:
+        parser.parseNotation("org.gradle:gradle-core") == new UnversionedModuleComponentSelector("org.gradle", "gradle-core")
+        parser.parseNotation(" foo:bar ") == new UnversionedModuleComponentSelector("foo", "bar")
+    }
+
+    def "parses module component identifier notation"() {
+        expect:
+        parser.parseNotation("org.gradle:gradle-core:1.+") == DefaultModuleComponentSelector.newSelector("org.gradle", "gradle-core", "1.+")
+        parser.parseNotation(" foo:bar:[1.3, 2.0)") == DefaultModuleComponentSelector.newSelector("foo", "bar", "[1.3, 2.0)")
+    }
+
+    def "reports invalid notation"() {
+        when: parser.parseNotation(notation)
+        then: thrown(UnsupportedNotationException)
+        where: notation << [null, "", ":", "foo", "foo:", "foo:bar:x:2", "  :", ":  ", "  :  "]
+    }
+
+    def "reports notation with invalid character for module"() {
+        when: parser.parseNotation("group:module${character}")
+        then: thrown(UnsupportedNotationException)
+        where: character << ["+", "*", "[", "]", "(", ")", ","]
+    }
+
+    def "reports notation with invalid character for module component"() {
+        when: parser.parseNotation("group:module${character}:1.0")
+        then: thrown(UnsupportedNotationException)
+        where: character << ["+", "*", "[", "]", "(", ")", ","]
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepositoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepositoryTest.groovy
index 87e2d11..dcae41e 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepositoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleComponentRepositoryTest.groovy
@@ -98,15 +98,15 @@ class CachingModuleComponentRepositoryTest extends Specification {
     }
 
     def "does not use cache when component metadata can be determined locally"() {
-        def dependency = Mock(DependencyMetaData)
         def componentId = Mock(ModuleComponentIdentifier)
+        def prescribedMetaData = Mock(ComponentOverrideMetadata)
         def result = new DefaultBuildableModuleComponentMetaDataResolveResult()
 
         when:
-        repo.localAccess.resolveComponentMetaData(dependency, componentId, result)
+        repo.localAccess.resolveComponentMetaData(componentId, prescribedMetaData, result)
 
         then:
-        realLocalAccess.resolveComponentMetaData(dependency, componentId, result) >> {
+        realLocalAccess.resolveComponentMetaData(componentId, prescribedMetaData, result) >> {
             result.resolved(Mock(MutableModuleComponentResolveMetaData))
         }
         0 * _
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy
index 41b1e19..3e7f423 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy
@@ -104,7 +104,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.requested >> selector
@@ -130,7 +130,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.requested >> selector
@@ -159,7 +159,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.getRequested() >> selector
@@ -189,7 +189,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.requested >> selector
@@ -222,7 +222,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, b, a], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, b, a], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.requested >> selector
@@ -246,7 +246,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.getRequested() >> selector
@@ -273,7 +273,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.getRequested() >> selector
@@ -302,7 +302,7 @@ class DefaultVersionedComponentChooserTest extends Specification {
         def selectedComponentResult = new DefaultBuildableComponentSelectionResult()
 
         when:
-        chooser.selectNewestMatchingComponent([c, a, b], dependency, selectedComponentResult)
+        chooser.selectNewestMatchingComponent([c, a, b], selectedComponentResult, dependency.getRequested())
 
         then:
         _ * dependency.getRequested() >> selector
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccessTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccessTest.groovy
index b544cc6..7f762ec 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccessTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleComponentRepositoryAccessTest.groovy
@@ -16,18 +16,17 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
-import org.gradle.internal.component.model.DependencyMetaData
 import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData
+import org.gradle.internal.component.model.ComponentOverrideMetadata
+import org.gradle.internal.component.model.DependencyMetaData
 import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult
 import spock.lang.Specification
 
 class IvyDynamicResolveModuleComponentRepositoryAccessTest extends Specification {
     final target = Mock(ModuleComponentRepositoryAccess)
     final metaData = Mock(MutableModuleComponentResolveMetaData)
-    final requestedDependency = Mock(DependencyMetaData)
+    final requestedDependency = Mock(ComponentOverrideMetadata)
     final moduleComponentId = Mock(ModuleComponentIdentifier)
     final result = Mock(BuildableModuleComponentMetaDataResolveResult)
     final ModuleComponentRepositoryAccess access = new IvyDynamicResolveModuleComponentRepositoryAccess(target)
@@ -41,10 +40,10 @@ class IvyDynamicResolveModuleComponentRepositoryAccessTest extends Specification
         result.metaData >> metaData
 
         when:
-        access.resolveComponentMetaData(requestedDependency, moduleComponentId, result)
+        access.resolveComponentMetaData(moduleComponentId, requestedDependency, result)
 
         then:
-        1 * target.resolveComponentMetaData(requestedDependency, moduleComponentId, result)
+        1 * target.resolveComponentMetaData(moduleComponentId, requestedDependency, result)
 
         and:
         1 * metaData.dependencies >> [original]
@@ -54,19 +53,17 @@ class IvyDynamicResolveModuleComponentRepositoryAccessTest extends Specification
 
     def "does nothing when dependency has not been resolved"() {
         when:
-        access.resolveComponentMetaData(requestedDependency, moduleComponentId, result)
+        access.resolveComponentMetaData(moduleComponentId, requestedDependency, result)
 
         then:
-        1 * target.resolveComponentMetaData(requestedDependency, moduleComponentId, result)
+        1 * target.resolveComponentMetaData(moduleComponentId, requestedDependency, result)
         _ * result.state >> BuildableModuleComponentMetaDataResolveResult.State.Missing
         0 * result._
     }
 
     def dependency(String revConstraint = '1.0') {
         def dep = Mock(DependencyMetaData)
-        def descriptor = Mock(DependencyDescriptor)
-        _ * descriptor.dynamicConstraintDependencyRevisionId >> IvyUtil.createModuleRevisionId('org', 'module', revConstraint)
-        _ * dep.descriptor >> descriptor
+        _ * dep.dynamicConstraintVersion >> revConstraint
         return dep
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapterTest.groovy
deleted file mode 100644
index 7cb5383..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainAdapterTest.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.DefaultModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme
-import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
-import org.gradle.internal.component.model.DependencyMetaData
-import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver
-import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult
-import spock.lang.Specification
-
-class RepositoryChainAdapterTest extends Specification {
-    def metaDataResolver = Mock(DependencyToComponentResolver)
-    def dynamicVersionResolver = Mock(DependencyToComponentIdResolver)
-    def idResult = Mock(BuildableComponentIdResolveResult)
-    def versionSelectorScheme = Stub(VersionSelectorScheme)
-    def requested = new DefaultModuleVersionSelector("group", "module", "version")
-    def id = new DefaultModuleComponentIdentifier("group", "module", "version")
-    def mvId = new DefaultModuleVersionIdentifier("group", "module", "version")
-    def dependency = Stub(DependencyMetaData) {
-        getRequested() >> requested
-    }
-    def resolver = new RepositoryChainAdapter(dynamicVersionResolver, metaDataResolver, versionSelectorScheme)
-
-    def "short-circuits static version resolution"() {
-        given:
-        versionSelectorScheme.parseSelector("version") >> {
-            Stub(VersionSelector) {
-                isDynamic() >> false
-            }
-        }
-
-        when:
-        resolver.resolve(dependency, idResult)
-
-        then:
-        1 * idResult.resolved(id, mvId)
-    }
-
-    def "resolves dynamic version"() {
-        given:
-        versionSelectorScheme.parseSelector("version") >> {
-            Stub(VersionSelector) {
-                isDynamic() >> true
-            }
-        }
-
-        when:
-        resolver.resolve(dependency, idResult)
-
-        then:
-        1 * dynamicVersionResolver.resolve(dependency, idResult)
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolverTest.groovy
new file mode 100644
index 0000000..29c183b
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainComponentMetaDataResolverTest.groovy
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.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.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
+import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData
+import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData
+import org.gradle.internal.component.model.ComponentOverrideMetadata
+import org.gradle.internal.component.model.DependencyMetaData
+import org.gradle.internal.resolve.ModuleVersionResolveException
+import org.gradle.internal.resolve.result.BuildableComponentResolveResult
+import spock.lang.Specification
+
+class RepositoryChainComponentMetaDataResolverTest extends Specification {
+    final metaData = metaData("1.2")
+    final moduleComponentId = DefaultModuleComponentIdentifier.newId("group", "project", "1.0")
+    final dependency = Stub(DependencyMetaData)
+    final componentRequestMetaData = Mock(ComponentOverrideMetadata)
+    final selector = DefaultModuleVersionSelector.newSelector("group", "project", "1.0")
+
+    final Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> transformer = Mock(Transformer)
+    final result = Mock(BuildableComponentResolveResult)
+    def localAccess = Mock(ModuleComponentRepositoryAccess)
+    def remoteAccess = Mock(ModuleComponentRepositoryAccess)
+    def localAccess2 = Mock(ModuleComponentRepositoryAccess)
+    def remoteAccess2 = Mock(ModuleComponentRepositoryAccess)
+
+    final VersionedComponentChooser componentSelectionStrategy = Mock(VersionedComponentChooser)
+    final RepositoryChainComponentMetaDataResolver resolver = new RepositoryChainComponentMetaDataResolver(componentSelectionStrategy, transformer)
+
+    ModuleVersionIdentifier moduleVersionIdentifier(ModuleDescriptor moduleDescriptor) {
+        def moduleRevId = moduleDescriptor.moduleRevisionId
+        new DefaultModuleVersionIdentifier(moduleRevId.organisation, moduleRevId.name, moduleRevId.revision)
+    }
+
+    def setup() {
+        _ * dependency.requested >> selector
+    }
+
+    def addRepo1() {
+        addModuleComponentRepository("repo1", localAccess, remoteAccess)
+    }
+
+    def addRepo2() {
+        addModuleComponentRepository("repo2", localAccess2, remoteAccess2)
+    }
+
+    def addModuleComponentRepository(def name, def repoLocalAccess, def repoRemoteAccess) {
+        def repo = Stub(ModuleComponentRepository) {
+            getLocalAccess() >> repoLocalAccess
+            getRemoteAccess() >> repoRemoteAccess
+            getName() >> name
+        }
+        resolver.add(repo)
+        repo
+    }
+
+    def "uses local dependency when available"() {
+        given:
+        def repo = addRepo1()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is unknown"() {
+        given:
+        def repo = addRepo1()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is probably missing"() {
+        given:
+        def repo = addRepo1()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "fails with not found when local static dependency is marked as missing"() {
+        given:
+        def repo = addRepo1()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.attempted("scheme:thing")
+            result.missing()
+        }
+        1 * result.attempted("scheme:thing")
+        1 * result.notFound(moduleComponentId)
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "fails with not found when local and remote static dependency marked as missing"() {
+        given:
+        addRepo1()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * result.notFound(moduleComponentId)
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "stops on first available local dependency for static version"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = Mock(ModuleComponentRepository)
+        resolver.add(repo2)
+        def repo3 = Mock(ModuleComponentRepository)
+        resolver.add(repo3)
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo1
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * repo2._
+        0 * repo3._
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * result._
+    }
+
+    def "uses local dependency when available in one repository and missing from all other repositories"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "uses local dependency when available in one repository and probably missing in all other repositories"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "uses remote dependency when local dependency is unknown for a given repository and probably missing in other repositories"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is probably missing in all repositories"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "does not attempt to resolve remote dependency when local dependency is missing"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is missing or unknown in all repositories"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+            result.authoritative = false
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo1
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "ignores failure to resolve local dependency when available in another repository"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.failed(new ModuleVersionResolveException(id, "broken"))
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "ignores failure to resolve remote dependency when available in another repository"() {
+        given:
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.failed(new ModuleVersionResolveException(id, "broken"))
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.resolved(metaData)
+        }
+        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
+            assert it.module == metaData
+            assert it.repository == repo2
+            metaData
+        }
+        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
+            assert metaData == this.metaData
+        }
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "rethrows failure to resolve local dependency when not available in any repository"() {
+        given:
+        def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken")
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.failed(failure)
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * result.failed({ it.cause == failure })
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        0 * result._
+    }
+
+    def "rethrows failure to resolve remote dependency when not available in any repository"() {
+        given:
+        def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken")
+        def repo1 = addRepo1()
+        def repo2 = addRepo2()
+
+        when:
+        resolver.resolve(moduleComponentId, componentRequestMetaData, result)
+
+        then:
+        1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.failed(failure)
+        }
+        1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _)
+        1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result ->
+            result.missing()
+        }
+        1 * result.failed({ it.cause == failure })
+
+        and:
+        0 * localAccess._
+        0 * remoteAccess._
+        0 * localAccess2._
+        0 * remoteAccess2._
+        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(MutableModuleComponentResolveMetaData) {
+            toString() >> version
+            getId() >> DefaultModuleVersionIdentifier.newId("org", "module", version)
+            getDescriptor() >> descriptor(version)
+        }
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy
deleted file mode 100644
index 43cb9b9..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainDependencyResolverTest.groovy
+++ /dev/null
@@ -1,582 +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.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.DefaultModuleVersionSelector
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
-import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
-import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData
-import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetaData
-import org.gradle.internal.component.model.DependencyMetaData
-import org.gradle.internal.resolve.ModuleVersionResolveException
-import org.gradle.internal.resolve.result.BuildableComponentResolveResult
-import spock.lang.Specification
-
-class RepositoryChainDependencyResolverTest extends Specification {
-    final metaData = metaData("1.2")
-    final moduleComponentId = DefaultModuleComponentIdentifier.newId("group", "project", "1.0")
-    final dependency = Stub(DependencyMetaData)
-    final selector = DefaultModuleVersionSelector.newSelector("group", "project", "1.0")
-    final moduleVersionId = DefaultModuleVersionIdentifier.newId("group", "project", "1.0")
-    final dependencyDescriptor = Stub(DependencyDescriptor)
-
-    final Transformer<ModuleComponentResolveMetaData, RepositoryChainModuleResolution> transformer = Mock(Transformer)
-    final result = Mock(BuildableComponentResolveResult)
-    def localAccess = Mock(ModuleComponentRepositoryAccess)
-    def remoteAccess = Mock(ModuleComponentRepositoryAccess)
-    def localAccess2 = Mock(ModuleComponentRepositoryAccess)
-    def remoteAccess2 = Mock(ModuleComponentRepositoryAccess)
-
-    final VersionedComponentChooser componentSelectionStrategy = Mock(VersionedComponentChooser)
-    final RepositoryChainDependencyResolver resolver = new RepositoryChainDependencyResolver(componentSelectionStrategy, transformer)
-
-    ModuleVersionIdentifier moduleVersionIdentifier(ModuleDescriptor moduleDescriptor) {
-        def moduleRevId = moduleDescriptor.moduleRevisionId
-        new DefaultModuleVersionIdentifier(moduleRevId.organisation, moduleRevId.name, moduleRevId.revision)
-    }
-
-    def setup() {
-        _ * dependency.requested >> selector
-        _ * dependency.descriptor >> dependencyDescriptor
-    }
-
-    def addRepo1() {
-        addModuleComponentRepository("repo1", localAccess, remoteAccess)
-    }
-
-    def addRepo2() {
-        addModuleComponentRepository("repo2", localAccess2, remoteAccess2)
-    }
-
-    def addModuleComponentRepository(def name, def repoLocalAccess, def repoRemoteAccess) {
-        def repo = Stub(ModuleComponentRepository) {
-            getLocalAccess() >> repoLocalAccess
-            getRemoteAccess() >> repoRemoteAccess
-            getName() >> name
-        }
-        resolver.add(repo)
-        repo
-    }
-
-    def "uses local dependency when available"() {
-        given:
-        def repo = addRepo1()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "attempts to find remote dependency when local dependency is unknown"() {
-        given:
-        def repo = addRepo1()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "attempts to find remote dependency when local dependency is probably missing"() {
-        given:
-        def repo = addRepo1()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "fails with not found when local static dependency is marked as missing"() {
-        given:
-        def repo = addRepo1()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.attempted("scheme:thing")
-            result.missing()
-        }
-        1 * result.attempted("scheme:thing")
-        1 * result.notFound(moduleVersionId)
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "fails with not found when local and remote static dependency marked as missing"() {
-        given:
-        addRepo1()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * result.notFound(moduleVersionId)
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "stops on first available local dependency for static version"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = Mock(ModuleComponentRepository)
-        resolver.add(repo2)
-        def repo3 = Mock(ModuleComponentRepository)
-        resolver.add(repo3)
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo1
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * repo2._
-        0 * repo3._
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * result._
-    }
-
-    def "uses local dependency when available in one repository and missing from all other repositories"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "uses local dependency when available in one repository and probably missing in all other repositories"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "uses remote dependency when local dependency is unknown for a given repository and probably missing in other repositories"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "attempts to find remote dependency when local dependency is probably missing in all repositories"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "does not attempt to resolve remote dependency when local dependency is missing"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "attempts to find remote dependency when local dependency is missing or unknown in all repositories"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-            result.authoritative = false
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo1
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "ignores failure to resolve local dependency when available in another repository"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.failed(new ModuleVersionResolveException(id, "broken"))
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "ignores failure to resolve remote dependency when available in another repository"() {
-        given:
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.failed(new ModuleVersionResolveException(id, "broken"))
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.resolved(metaData)
-        }
-        1 * transformer.transform(_) >> { RepositoryChainModuleResolution it ->
-            assert it.module == metaData
-            assert it.repository == repo2
-            metaData
-        }
-        1 * result.resolved(_) >> { ModuleComponentResolveMetaData metaData ->
-            assert metaData == this.metaData
-        }
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "rethrows failure to resolve local dependency when not available in any repository"() {
-        given:
-        def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken")
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.failed(failure)
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * result.failed({ it.cause == failure })
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        0 * result._
-    }
-
-    def "rethrows failure to resolve remote dependency when not available in any repository"() {
-        given:
-        def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken")
-        def repo1 = addRepo1()
-        def repo2 = addRepo2()
-
-        when:
-        resolver.resolve(dependency, result)
-
-        then:
-        1 * localAccess.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.failed(failure)
-        }
-        1 * localAccess2.resolveComponentMetaData(dependency, moduleComponentId, _)
-        1 * remoteAccess2.resolveComponentMetaData(dependency, moduleComponentId, _) >> { dep, id, result ->
-            result.missing()
-        }
-        1 * result.failed({ it.cause == failure })
-
-        and:
-        0 * localAccess._
-        0 * remoteAccess._
-        0 * localAccess2._
-        0 * remoteAccess2._
-        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(MutableModuleComponentResolveMetaData) {
-            toString() >> version
-            getId() >> DefaultModuleVersionIdentifier.newId("org", "module", version)
-            getDescriptor() >> descriptor(version)
-        }
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactoryTest.groovy
index 80dd9f4..6903a1b 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactoryTest.groovy
@@ -19,7 +19,6 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 import com.google.common.collect.Lists
 import org.gradle.api.internal.artifacts.ComponentMetadataProcessor
 import org.gradle.api.internal.artifacts.ComponentSelectionRulesInternal
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache
@@ -76,7 +75,7 @@ class ResolveIvyFactoryTest extends Specification {
 
     def "returns an empty resolver when no repositories are configured" () {
         when:
-        def resolver = resolveIvyFactory.create(Stub(ConfigurationInternal), Collections.emptyList(), Stub(ComponentMetadataProcessor))
+        def resolver = resolveIvyFactory.create(Stub(ResolutionStrategyInternal), Collections.emptyList(), Stub(ComponentMetadataProcessor))
 
         then:
         resolver instanceof NoRepositoriesResolver
@@ -85,10 +84,8 @@ class ResolveIvyFactoryTest extends Specification {
     def "sets parent resolver with different selection rules when repository is external" () {
         def componentSelectionRules = Stub(ComponentSelectionRulesInternal)
 
-        def configuration = Stub(ConfigurationInternal) {
-            getResolutionStrategy() >> Stub(ResolutionStrategyInternal) {
-                getComponentSelection() >> componentSelectionRules
-            }
+        def resolutionStrategy = Stub(ResolutionStrategyInternal) {
+            getComponentSelection() >> componentSelectionRules
         }
 
         def spyResolver = externalResourceResolverSpy()
@@ -97,7 +94,7 @@ class ResolveIvyFactoryTest extends Specification {
         })
 
         when:
-        def resolver = resolveIvyFactory.create(configuration, repositories, Stub(ComponentMetadataProcessor))
+        def resolver = resolveIvyFactory.create(resolutionStrategy, repositories, Stub(ComponentMetadataProcessor))
 
         then:
         assert resolver instanceof UserResolverChain
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepositoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepositoryTest.groovy
index f7d0d5e..29b421a 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepositoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/memcache/InMemoryCachedModuleComponentRepositoryTest.groovy
@@ -15,23 +15,25 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepository
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepositoryAccess
 import org.gradle.api.internal.component.ArtifactType
-import org.gradle.internal.component.model.ModuleSource
-import org.gradle.internal.resolve.result.BuildableArtifactResolveResult
-import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult
-import org.gradle.internal.component.model.ComponentUsage
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.*
-import org.gradle.internal.component.model.DependencyMetaData
 import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier
 import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData
 import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData
+import org.gradle.internal.component.model.ComponentOverrideMetadata
+import org.gradle.internal.component.model.ComponentUsage
+import org.gradle.internal.component.model.DependencyMetaData
+import org.gradle.internal.component.model.ModuleSource
+import org.gradle.internal.resolve.result.BuildableArtifactResolveResult
+import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult
 import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult
 import org.gradle.internal.resolve.result.BuildableModuleVersionListingResolveResult
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
-class InMemoryCachedModuleComponentRepositoryTest extends Specification {
+class   InMemoryCachedModuleComponentRepositoryTest extends Specification {
 
     def stats = new InMemoryCacheStats()
     def localArtifactsCache = Mock(InMemoryArtifactsCache)
@@ -49,6 +51,8 @@ class InMemoryCachedModuleComponentRepositoryTest extends Specification {
     def lib = Mock(ModuleComponentIdentifier)
     def selector = newSelector("org", "lib", "1.0")
     def dep = Stub(DependencyMetaData) { getRequested() >> selector }
+    def componentRequestMetaData = Mock(ComponentOverrideMetadata)
+
 
     def listingResult = Mock(BuildableModuleVersionListingResolveResult)
     def metaDataResult = Mock(BuildableModuleComponentMetaDataResolveResult)
@@ -103,18 +107,18 @@ class InMemoryCachedModuleComponentRepositoryTest extends Specification {
 
     def "retrieves and caches local dependencies"() {
         when:
-        repo.localAccess.resolveComponentMetaData(dep, lib, metaDataResult)
+        repo.localAccess.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
 
         then:
         1 * localMetaDataCache.supplyMetaData(lib, metaDataResult) >> false
-        1 * localDelegate.resolveComponentMetaData(dep, lib, metaDataResult)
+        1 * localDelegate.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
         1 * localMetaDataCache.newDependencyResult(lib, metaDataResult)
         0 * _
     }
 
     def "uses local dependencies from cache"() {
         when:
-        repo.localAccess.resolveComponentMetaData(dep, lib, metaDataResult)
+        repo.localAccess.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
 
         then:
         1 * localMetaDataCache.supplyMetaData(lib, metaDataResult) >> true
@@ -123,18 +127,18 @@ class InMemoryCachedModuleComponentRepositoryTest extends Specification {
 
     def "retrieves and caches dependencies"() {
         when:
-        repo.remoteAccess.resolveComponentMetaData(dep, lib, metaDataResult)
+        repo.remoteAccess.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
 
         then:
         1 * remoteMetaDataCache.supplyMetaData(lib, metaDataResult) >> false
-        1 * remoteDelegate.resolveComponentMetaData(dep, lib, metaDataResult)
+        1 * remoteDelegate.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
         1 * remoteMetaDataCache.newDependencyResult(lib, metaDataResult)
         0 * _
     }
 
     def "uses dependencies from cache"() {
         when:
-        repo.remoteAccess.resolveComponentMetaData(dep, lib, metaDataResult)
+        repo.remoteAccess.resolveComponentMetaData(lib, componentRequestMetaData, metaDataResult)
 
         then:
         1 * remoteMetaDataCache.supplyMetaData(lib, metaDataResult) >> true
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy
index 451d612..1a38266 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserProfileTest.groovy
@@ -223,8 +223,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -422,8 +422,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -493,7 +493,7 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -654,8 +654,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -740,8 +740,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -810,7 +810,7 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -1030,8 +1030,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -1235,8 +1235,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -1308,7 +1308,7 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -1475,8 +1475,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -1563,8 +1563,8 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent)) }
 
         when:
         def descriptor = parsePom()
@@ -1635,7 +1635,7 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -1722,7 +1722,7 @@ class GradlePomModuleDescriptorParserProfileTest extends AbstractGradlePomModule
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         def metaData = parseMetaData()
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
index eef9172..54e0ba7 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
@@ -449,8 +449,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -518,7 +518,7 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -710,8 +710,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -788,8 +788,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -871,8 +871,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -1017,8 +1017,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -1121,11 +1121,11 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
@@ -1336,7 +1336,7 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         def metaData = parseMetaData()
@@ -1461,8 +1461,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -1530,8 +1530,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -1604,8 +1604,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'grandparent' }, MAVEN_POM) >> {
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'grandparent' }, MAVEN_POM) >> {
             new DefaultLocallyAvailableExternalResource(grandParent.toURI(), new DefaultLocallyAvailableResource(grandParent))
         }
 
@@ -1687,8 +1687,8 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
-        parseContext.getMetaDataArtifact({ it.name == 'relocated' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(relocated.toURI(), new DefaultLocallyAvailableResource(relocated)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'relocated' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(relocated.toURI(), new DefaultLocallyAvailableResource(relocated)) }
 
 
         when:
@@ -1761,7 +1761,7 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         def descriptor = parsePom()
@@ -1828,7 +1828,7 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
+        parseContext.getMetaDataArtifact({ it.module == 'parent' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(parent.toURI(), new DefaultLocallyAvailableResource(parent)) }
 
         when:
         parsePom()
@@ -2148,7 +2148,7 @@ class GradlePomModuleDescriptorParserTest extends AbstractGradlePomModuleDescrip
 </project>
 """
         and:
-        parseContext.getMetaDataArtifact({ it.name == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
+        parseContext.getMetaDataArtifact({ it.module == 'imported' }, MAVEN_POM) >> { new DefaultLocallyAvailableExternalResource(imported.toURI(), new DefaultLocallyAvailableResource(imported)) }
 
         when:
         def descriptor = parsePom()
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
index 0d1f487..ebed744 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepository
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver
+
 import org.gradle.internal.resource.local.LocallyAvailableResource
 import org.gradle.internal.resource.local.PathKeyFileStore
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -39,7 +39,6 @@ class ModuleDescriptorStoreTest extends Specification {
     IvyModuleDescriptorWriter ivyModuleDescriptorWriter = Mock()
     IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser = Mock()
     ModuleComponentIdentifier moduleComponentIdentifier = Mock()
-    def resolver = Mock(DependencyToComponentResolver)
 
     def setup() {
         store = new ModuleDescriptorStore(pathKeyFileStore, ivyModuleDescriptorWriter, ivyXmlModuleDescriptorParser);
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy
index 368ac51..2576a88 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToArtifactsConverterTest.groovy
@@ -15,14 +15,8 @@
  */
 
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter
-
 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.DefaultModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet
-import org.gradle.internal.component.model.IvyArtifactName
+import org.gradle.api.artifacts.PublishArtifactSet
 import org.gradle.internal.component.local.model.MutableLocalComponentMetaData
 import spock.lang.Specification
 
@@ -33,68 +27,22 @@ class DefaultConfigurationsToArtifactsConverterTest extends Specification {
         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")
+        def artifacts1 = Stub(PublishArtifactSet)
+        def artifacts2 = Stub(PublishArtifactSet)
 
         given:
         config1.name >> "config1"
-        config1.artifacts >> new DefaultPublishArtifactSet("art1", new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact, [artifact1]))
+        config1.artifacts >> artifacts1
         config2.name >> "config2"
-        config2.artifacts >> new DefaultPublishArtifactSet("art1", new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact, [artifact2]))
+        config2.artifacts >> artifacts2
 
-        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, IvyArtifactName artifact, file ->
-            assert artifact.name == 'art1'
-            assert artifact.type == 'type1'
-            assert artifact.extension == 'ext1'
-            assert artifact.attributes == [:]
-        }
-        1 * metaData.addArtifact("config2", _, file2) >> { name, IvyArtifactName artifact, file ->
-            assert artifact.name == 'art2'
-            assert artifact.type == 'type2'
-            assert artifact.extension == 'ext2'
-            assert artifact.attributes == [(Dependency.CLASSIFIER): 'classifier']
-        }
-        _ * metaData.id
-        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, IvyArtifactName ivyArtifact, f ->
-            assert ivyArtifact.name == 'module'
-        }
-        _ * metaData.id >> DefaultModuleVersionIdentifier.newId("group", "module", "version")
+        1 * metaData.addArtifacts("config1", artifacts1)
+        1 * metaData.addArtifacts("config2", artifacts2)
         0 * metaData._
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
index 3516f09..fe44ae4 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
@@ -15,9 +15,13 @@
  */
 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.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
 import org.gradle.internal.component.local.model.DefaultLocalComponentMetaData;
 import org.gradle.internal.component.local.model.MutableLocalComponentMetaData;
 import org.gradle.util.TestUtil;
@@ -28,8 +32,6 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Collections;
-
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
@@ -38,16 +40,18 @@ public class DefaultConfigurationsToModuleDescriptorConverterTest {
     private DefaultConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter = new DefaultConfigurationsToModuleDescriptorConverter();
 
     private JUnit4Mockery context = new JUnit4Mockery();
+    private ModuleComponentIdentifier componentId = DefaultModuleComponentIdentifier.newId("org", "name", "rev");
+    private ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(componentId);
 
     @Test
     public void testAddConfigurations() {
         Configuration configurationStub1 = createNamesAndExtendedConfigurationStub("conf1");
         Configuration configurationStub2 = createNamesAndExtendedConfigurationStub("conf2", configurationStub1);
-        DefaultModuleDescriptor moduleDescriptor = TestUtil.createModuleDescriptor(Collections.EMPTY_SET);
-        MutableLocalComponentMetaData metaData = new DefaultLocalComponentMetaData(moduleDescriptor, null);
+        MutableLocalComponentMetaData metaData = new DefaultLocalComponentMetaData(id, componentId, "status");
 
         configurationsToModuleDescriptorConverter.addConfigurations(metaData, WrapUtil.toSet(configurationStub1, configurationStub2));
 
+        ModuleDescriptor moduleDescriptor = metaData.toPublishMetaData().getModuleDescriptor();
         assertIvyConfigurationIsCorrect(moduleDescriptor.getConfiguration(configurationStub1.getName()),
                 expectedIvyConfiguration(configurationStub1));
         assertIvyConfigurationIsCorrect(moduleDescriptor.getConfiguration(configurationStub2.getName()),
@@ -88,6 +92,9 @@ public class DefaultConfigurationsToModuleDescriptorConverterTest {
 
             allowing(configurationStub).getExtendsFrom();
             will(returnValue(WrapUtil.toSet(extendsFromConfigurations)));
+
+            allowing(configurationStub).getHierarchy();
+            will(returnValue(WrapUtil.toSet(extendsFromConfigurations)));
         }});
         return configurationStub;
     }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy
index 7faca5b..0dfa450 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveLocalComponentFactoryTest.groovy
@@ -20,8 +20,8 @@ import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.internal.artifacts.DefaultModule
 import org.gradle.api.internal.artifacts.component.ComponentIdentifierFactory
-import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
 import org.gradle.internal.component.local.model.DefaultLocalComponentMetaData
 import spock.lang.Specification
 
@@ -43,7 +43,7 @@ public class ResolveLocalComponentFactoryTest extends Specification {
         def module = new DefaultModule('group-one', 'name-one', 'version-one')
 
         when:
-        def actualDescriptor = resolveModuleDescriptorConverter.convert(configurations, module);
+        def componentMetaData = resolveModuleDescriptorConverter.convert(new ComponentConverterSource(configurations, module))
 
         then:
         1 * configurationsConverter.addConfigurations(!null, configurations)
@@ -51,8 +51,8 @@ public class ResolveLocalComponentFactoryTest extends Specification {
         1 * componentIdentifierFactory.createComponentIdentifier(module) >> new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
 
         and:
-        actualDescriptor instanceof DefaultLocalComponentMetaData
-        actualDescriptor.moduleDescriptor instanceof DefaultModuleDescriptor
-        actualDescriptor.toResolveMetaData().componentId == new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
+        componentMetaData instanceof DefaultLocalComponentMetaData
+        componentMetaData.toResolveMetaData().componentId == new DefaultModuleComponentIdentifier('group-one', 'name-one', 'version-one')
+        componentMetaData.toPublishMetaData().moduleDescriptor instanceof DefaultModuleDescriptor
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
index 8ee1970..5184088 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
@@ -18,8 +18,6 @@ package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencie
 
 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.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.id.ArtifactId;
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
@@ -30,6 +28,8 @@ 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.internal.component.model.DependencyMetaData;
+import org.gradle.internal.component.model.IvyArtifactName;
 import org.gradle.util.TestUtil;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
@@ -38,8 +38,7 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
@@ -80,38 +79,31 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
                 setTransitive(true);
     }
 
-    protected void assertDependencyDescriptorHasCommonFixtureValues(DependencyDescriptor dependencyDescriptor) {
-        assertThat(dependencyDescriptor.getParentRevisionId(), equalTo(moduleDescriptor.getModuleRevisionId()));
-        assertEquals(TEST_IVY_EXCLUDE_RULE, dependencyDescriptor.getExcludeRules(TEST_CONF)[0]);
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), equalTo(WrapUtil.toArray(TEST_DEP_CONF)));
-        assertThat(dependencyDescriptor.isTransitive(), equalTo(true));
-        assertDependencyDescriptorHasArtifacts(dependencyDescriptor);
+    protected void assertDependencyDescriptorHasCommonFixtureValues(DependencyMetaData dependencyMetaData) {
+        assertEquals(TEST_IVY_EXCLUDE_RULE, dependencyMetaData.getExcludeRules(Collections.singleton(TEST_CONF))[0]);
+        assertThat(dependencyMetaData.getDependencyConfigurations(TEST_CONF, TEST_CONF), equalTo(WrapUtil.toArray(TEST_DEP_CONF)));
+        assertThat(dependencyMetaData.isTransitive(), equalTo(true));
+        assertDependencyDescriptorHasArtifacts(dependencyMetaData);
     }
 
-    private void assertDependencyDescriptorHasArtifacts(DependencyDescriptor dependencyDescriptor) {
-        List<DependencyArtifactDescriptor> artifactDescriptors = WrapUtil.toList(dependencyDescriptor.getDependencyArtifacts(TEST_CONF));
+    private void assertDependencyDescriptorHasArtifacts(DependencyMetaData dependencyMetaData) {
+        List<IvyArtifactName> artifactDescriptors = WrapUtil.toList(dependencyMetaData.getArtifacts());
         assertThat(artifactDescriptors.size(), equalTo(2));
 
-        
-        DependencyArtifactDescriptor artifactDescriptorWithoutClassifier = findDescriptor(artifactDescriptors, artifact);
-        assertEquals(new HashMap(), artifactDescriptorWithoutClassifier.getExtraAttributes());
-        assertEquals(null, artifactDescriptorWithoutClassifier.getUrl());
+        IvyArtifactName artifactDescriptorWithoutClassifier = findDescriptor(artifactDescriptors, artifact);
+        assertEquals(null, artifactDescriptorWithoutClassifier.getClassifier());
+        assertEquals(new HashMap(), artifactDescriptorWithoutClassifier.getAttributes());
         compareArtifacts(artifact, artifactDescriptorWithoutClassifier);
-        assertEquals(artifact.getType(), artifactDescriptorWithoutClassifier.getExt());
+        assertEquals(artifact.getType(), artifactDescriptorWithoutClassifier.getExtension());
 
-        DependencyArtifactDescriptor artifactDescriptorWithClassifierAndConfs = findDescriptor(artifactDescriptors, artifactWithClassifiers);
-        assertEquals(WrapUtil.toMap(Dependency.CLASSIFIER, artifactWithClassifiers.getClassifier()), artifactDescriptorWithClassifierAndConfs.getQualifiedExtraAttributes());
+        IvyArtifactName artifactDescriptorWithClassifierAndConfs = findDescriptor(artifactDescriptors, artifactWithClassifiers);
+        assertEquals(artifactWithClassifiers.getClassifier(), artifactDescriptorWithClassifierAndConfs.getClassifier());
         compareArtifacts(artifactWithClassifiers, artifactDescriptorWithClassifierAndConfs);
-        assertEquals(artifactWithClassifiers.getExtension(), artifactDescriptorWithClassifierAndConfs.getExt());
-        try {
-            assertEquals(new URL(artifactWithClassifiers.getUrl()), artifactDescriptorWithClassifierAndConfs.getUrl());
-        } catch (MalformedURLException e) {
-            throw new RuntimeException(e);
-        }
+        assertEquals(artifactWithClassifiers.getExtension(), artifactDescriptorWithClassifierAndConfs.getExtension());
     }
 
-    private DependencyArtifactDescriptor findDescriptor(List<DependencyArtifactDescriptor> artifactDescriptors, DefaultDependencyArtifact dependencyArtifact) {
-        for (DependencyArtifactDescriptor artifactDescriptor : artifactDescriptors) {
+    private IvyArtifactName findDescriptor(List<IvyArtifactName> artifactDescriptors, DefaultDependencyArtifact dependencyArtifact) {
+        for (IvyArtifactName artifactDescriptor : artifactDescriptors) {
             if (artifactDescriptor.getName().equals(dependencyArtifact.getName())) {
                 return artifactDescriptor;
             }
@@ -119,7 +111,7 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
         throw new RuntimeException("Descriptor could not be found");
     }
 
-    private void compareArtifacts(DependencyArtifact artifact, DependencyArtifactDescriptor artifactDescriptor) {
+    private void compareArtifacts(DependencyArtifact artifact, IvyArtifactName artifactDescriptor) {
         assertEquals(artifact.getName(), artifactDescriptor.getName());
         assertEquals(artifact.getType(), artifactDescriptor.getType());
     }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.groovy
index ce63e9c..be117a6 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.groovy
@@ -61,7 +61,7 @@ public class DefaultDependenciesToModuleDescriptorConverterTest extends Specific
         1 * configuration.dependencies >> dependencySet
         1 * dependencySet.withType(ModuleDependency) >> toDomainObjectSet(ModuleDependency, dependency)
         1 * configuration.name >> "config"
-        1 * dependencyDescriptorFactory.createDependencyDescriptor("config", descriptor, dependency) >> dependencyDescriptor
+        1 * dependencyDescriptorFactory.createDependencyDescriptor("config", dependency) >> dependencyDescriptor
         1 * metaData.addDependency(dependencyDescriptor)
         1 * configuration.excludeRules >> ([] as Set)
         0 * _
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy
index d8e78c5..1617828 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy
@@ -36,7 +36,7 @@ public class DefaultDependencyDescriptorFactoryTest extends Specification {
         def dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
                 ivyDependencyDescriptorFactory1, ivyDependencyDescriptorFactory2
         );
-        def created = dependencyDescriptorFactory.createDependencyDescriptor(configurationName, moduleDescriptor, projectDependency)
+        def created = dependencyDescriptorFactory.createDependencyDescriptor(configurationName, projectDependency)
 
         then:
         created == result
@@ -44,7 +44,7 @@ public class DefaultDependencyDescriptorFactoryTest extends Specification {
         and:
         1 * ivyDependencyDescriptorFactory1.canConvert(projectDependency) >> false
         1 * ivyDependencyDescriptorFactory2.canConvert(projectDependency) >> true
-        1 * ivyDependencyDescriptorFactory2.createDependencyDescriptor(configurationName, projectDependency, moduleDescriptor) >> result
+        1 * ivyDependencyDescriptorFactory2.createDependencyDescriptor(configurationName, projectDependency) >> result
     }
 
     def "fails where no internal factory can handle dependency type"() {
@@ -57,7 +57,7 @@ public class DefaultDependencyDescriptorFactoryTest extends Specification {
         def dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
                 ivyDependencyDescriptorFactory1
         );
-        dependencyDescriptorFactory.createDependencyDescriptor(configurationName, moduleDescriptor, projectDependency)
+        dependencyDescriptorFactory.createDependencyDescriptor(configurationName, projectDependency)
 
         then:
         thrown InvalidUserDataException
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
index 9c96e6d..4b7252c 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
@@ -16,12 +16,10 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.artifacts.ExternalModuleDependency;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
 import org.gradle.internal.component.local.model.DslOriginDependencyMetaData;
 import org.hamcrest.Matchers;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -46,8 +44,10 @@ public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDepen
     @Test
     public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
         ModuleDependency dependency = new DefaultExternalModuleDependency(null, "gradle-core", null, TEST_DEP_CONF);
-        DslOriginDependencyMetaData dependencyMetaData = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, dependency, moduleDescriptor);
-        assertThat(dependencyMetaData.getDescriptor().getDependencyRevisionId(), equalTo(IvyUtil.createModuleRevisionId(dependency)));
+        DslOriginDependencyMetaData dependencyMetaData = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, dependency);
+        assertThat(dependencyMetaData.getRequested().getGroup(), equalTo(""));
+        assertThat(dependencyMetaData.getRequested().getName(), equalTo("gradle-core"));
+        assertThat(dependencyMetaData.getRequested().getVersion(), equalTo(""));
     }
 
     @Test
@@ -56,12 +56,13 @@ public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDepen
                 "gradle-core", "1.0", TEST_DEP_CONF);
         setUpDependency(moduleDependency);
 
-        DslOriginDependencyMetaData dependencyMetaData = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, moduleDependency, moduleDescriptor);
+        DslOriginDependencyMetaData dependencyMetaData = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, moduleDependency);
 
-        DependencyDescriptor dependencyDescriptor = dependencyMetaData.getDescriptor();
-        assertEquals(moduleDependency.isChanging(), dependencyDescriptor.isChanging());
-        assertEquals(dependencyDescriptor.isForce(), moduleDependency.isForce());
-        assertEquals(IvyUtil.createModuleRevisionId(moduleDependency), dependencyDescriptor.getDependencyRevisionId());
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertEquals(moduleDependency.isChanging(), dependencyMetaData.isChanging());
+        assertEquals(moduleDependency.isForce(), dependencyMetaData.isForce());
+        assertEquals(moduleDependency.getGroup(), dependencyMetaData.getRequested().getGroup());
+        assertEquals(moduleDependency.getName(), dependencyMetaData.getRequested().getName());
+        assertEquals(moduleDependency.getVersion(), dependencyMetaData.getRequested().getVersion());
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyMetaData);
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy
index 51babb2..1a0ac13 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.groovy
@@ -16,8 +16,8 @@
 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.DefaultModuleVersionSelector
 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.internal.component.local.model.DslOriginDependencyMetaData
@@ -44,13 +44,12 @@ public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDe
     public void testCreateFromProjectDependency() {
         ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
         setUpDependency(projectDependency);
-        DslOriginDependencyMetaData dependencyMetaData = projectDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, projectDependency, moduleDescriptor);
+        DslOriginDependencyMetaData dependencyMetaData = projectDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, projectDependency);
 
-        def dependencyDescriptor = dependencyMetaData.descriptor
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertFalse(dependencyDescriptor.isChanging());
-        assertFalse(dependencyDescriptor.isForce());
-        assertEquals(IvyUtil.createModuleRevisionId("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyMetaData);
+        assertFalse(dependencyMetaData.isChanging());
+        assertFalse(dependencyMetaData.isForce());
+        assertEquals(DefaultModuleVersionSelector.newSelector("someGroup", "test", "someVersion"), dependencyMetaData.getRequested());
         assertSame(projectDependency, dependencyMetaData.source);
     }
 
@@ -58,6 +57,7 @@ public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDe
         AbstractProject dependencyProject = TestUtil.createRootProject();
         dependencyProject.setGroup("someGroup");
         dependencyProject.setVersion("someVersion");
+        dependencyProject.configurations.create(dependencyConfiguration)
         return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, {} as ProjectAccessListener, true);
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
index 2b31818..85c5cce 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
@@ -14,22 +14,29 @@
  * limitations under the License.
  */
 package org.gradle.api.internal.artifacts.ivyservice.projectmodule
-
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.gradle.api.artifacts.component.ComponentIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory
 import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData
+import org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier
 import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
 import org.gradle.internal.component.local.model.MutableLocalComponentMetaData
+import org.gradle.internal.component.model.ComponentOverrideMetadata
+import org.gradle.internal.component.model.DefaultComponentOverrideMetadata
 import org.gradle.internal.component.model.DependencyMetaData
+import org.gradle.internal.resolve.ModuleVersionResolveException
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver
 import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult
+import org.gradle.internal.resolve.result.BuildableComponentResolveResult
 import spock.lang.Specification
 
 class ProjectDependencyResolverTest extends Specification {
     final ProjectComponentRegistry registry = Mock()
     final DependencyToComponentIdResolver target = Mock()
+    final ComponentMetaDataResolver componentResolver = Mock()
     final LocalComponentFactory converter = Mock()
-    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, converter, target)
+    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, converter, target, componentResolver)
 
     def "resolves project dependency"() {
         setup:
@@ -51,6 +58,24 @@ class ProjectDependencyResolverTest extends Specification {
         0 * result._
     }
 
+    def "resolves project component"() {
+        setup:
+        def resolveMetaData = Stub(ModuleComponentResolveMetaData)
+        def componentMetaData = Stub(MutableLocalComponentMetaData) {
+            toResolveMetaData() >> resolveMetaData
+        }
+        def result = Mock(BuildableComponentResolveResult)
+        def projectComponentId = new DefaultProjectComponentIdentifier(":projectPath")
+
+        when:
+        resolver.resolve(projectComponentId, new DefaultComponentOverrideMetadata(), result)
+
+        then:
+        1 * registry.getProject(":projectPath") >> componentMetaData
+        1 * result.resolved(resolveMetaData)
+        0 * result._
+    }
+
     def "delegates to backing resolver for non-project dependency"() {
         def result = Mock(BuildableComponentIdResolveResult)
         def dependencyDescriptor = Stub(DependencyDescriptor)
@@ -65,4 +90,34 @@ class ProjectDependencyResolverTest extends Specification {
         1 * target.resolve(dependencyMetaData, result)
         0 * _
     }
+
+    def "delegates to backing resolver for non-project identifier"() {
+        def result = Mock(BuildableComponentResolveResult)
+        def componentIdentifier = Mock(ComponentIdentifier)
+        def overrideMetaData = Mock(ComponentOverrideMetadata)
+
+        when:
+        resolver.resolve(componentIdentifier, overrideMetaData, result)
+
+        then:
+        1 * componentResolver.resolve(componentIdentifier, overrideMetaData, result)
+        0 * _
+    }
+
+    def "adds failure to resolution result if project does not exist"() {
+        def result = Mock(BuildableComponentResolveResult)
+        def componentIdentifier = new DefaultProjectComponentIdentifier(":doesnotexist")
+        def overrideMetaData = Mock(ComponentOverrideMetadata)
+
+        when:
+        registry.getProject(_) >> null
+        and:
+        resolver.resolve(componentIdentifier, overrideMetaData, result)
+
+        then:
+        1 * result.failed(_) >> { ModuleVersionResolveException failure ->
+            assert failure.message == "project ':doesnotexist' not found."
+        }
+        0 * _
+    }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
index a459b1e..50b622a 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
@@ -216,7 +216,7 @@ public class DefaultCachePolicySpec extends Specification {
     def "mutation is checked"() {
         def validator = Mock(MutationValidator)
         given:
-        cachePolicy.beforeChange(validator)
+        cachePolicy.setMutationValidator(validator)
 
         when: cachePolicy.cacheChangingModulesFor(0, TimeUnit.HOURS)
         then: (1.._) * validator.validateMutation(STRATEGY)
@@ -237,7 +237,7 @@ public class DefaultCachePolicySpec extends Specification {
     def "mutation is not checked for copy"() {
         def validator = Mock(MutationValidator)
         given:
-        cachePolicy.beforeChange(validator)
+        cachePolicy.setMutationValidator(validator)
         def copy = cachePolicy.copy()
 
         when: copy.cacheChangingModulesFor(0, TimeUnit.HOURS)
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRulesTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRulesTest.groovy
index 9a88d1a..217279b 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRulesTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultComponentSelectionRulesTest.groovy
@@ -269,7 +269,7 @@ class DefaultComponentSelectionRulesTest extends Specification {
 
     def "mutation is checked for public API"() {
         def checker = Mock(MutationValidator)
-        rules.beforeChange(checker)
+        rules.setMutationValidator(checker)
 
         when: rules.all(Actions.doNothing())
         then: 1 * checker.validateMutation(STRATEGY)
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutionsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutionsSpec.groovy
deleted file mode 100644
index 24b87b4..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultDependencySubstitutionsSpec.groovy
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.Action
-import org.gradle.api.Project
-import org.gradle.api.artifacts.ModuleDependencySubstitution
-import org.gradle.api.artifacts.ProjectDependencySubstitution
-import org.gradle.api.internal.artifacts.*
-import org.gradle.api.internal.artifacts.configurations.MutationValidator
-import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
-import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
-import spock.lang.Specification
-import spock.lang.Unroll
-
-import static org.gradle.api.internal.artifacts.configurations.MutationValidator.MutationType.STRATEGY
-
-class DefaultDependencySubstitutionsSpec extends Specification {
-    DependencySubstitutionsInternal substitutions;
-
-    def setup() {
-        substitutions = new DefaultDependencySubstitutions()
-    }
-
-    def "provides no op resolve rule when no rules or forced modules configured"() {
-        given:
-        def details = Mock(DependencySubstitutionInternal)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(details)
-
-        then:
-        0 * details._
-    }
-
-    def "all() matches modules and projects"() {
-        given:
-        def action = Mock(Action)
-        substitutions.all(action)
-
-        def moduleDetails = Mock(ModuleDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
-        1 * action.execute(moduleDetails)
-        0 * _
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        1 * action.execute(projectDetails)
-        0 * _
-    }
-
-    def "allWithDependencyResolveDetails() wraps substitution in legacy format"() {
-        given:
-        def action = Mock(Action)
-        substitutions.allWithDependencyResolveDetails(action)
-
-        def moduleOldRequested = DefaultModuleVersionSelector.newSelector("org.utils", "api", "1.5")
-        def moduleTarget = DefaultModuleComponentSelector.newSelector(moduleOldRequested)
-        def moduleDetails = Mock(ModuleDependencySubstitutionInternal)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.target >> moduleTarget
-        _ * moduleDetails.oldRequested >> moduleOldRequested
-        1 * action.execute({ DependencyResolveDetailsInternal details ->
-            details.requested == moduleOldRequested
-        })
-        0 * _
-
-        def projectOldRequested = DefaultModuleVersionSelector.newSelector("org.utils", "api", "1.5")
-        def projectTarget = DefaultProjectComponentSelector.newSelector(":api")
-        def projectDetails = Mock(ProjectDependencySubstitutionInternal)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.target >> projectTarget
-        _ * projectDetails.oldRequested >> projectOldRequested
-        1 * action.execute({ DependencyResolveDetailsInternal details ->
-            details.requested == projectOldRequested
-        })
-        0 * _
-    }
-
-    def "eachModule() matches only modules"() {
-        given:
-        def action = Mock(Action)
-        substitutions.eachModule(action)
-
-        def moduleDetails = Mock(ModuleDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
-        1 * action.execute(moduleDetails)
-        0 * _
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        0 * _
-    }
-
-    @Unroll
-    def "withModule() matches only given module: #matchingModule"() {
-        given:
-        def matchingModuleVersionAction = Mock(Action)
-        def nonMatchingModuleVersionAction = Mock(Action)
-
-        substitutions.withModule(matchingModule, matchingModuleVersionAction)
-        substitutions.withModule(nonMatchingModule, nonMatchingModuleVersionAction)
-
-        def moduleDetails = Mock(ModuleDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
-        1 * matchingModuleVersionAction.execute(moduleDetails)
-        0 * nonMatchingModuleVersionAction.execute(_)
-        0 * _
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        0 * _
-
-        where:
-        matchingModule                    | nonMatchingModule
-        "org.utils:api"                   | "org.utils:impl"
-        [group: "org.utils", name: "api"] | [group: "org.utils", name: "impl"]
-    }
-
-    def "withModule() does not match projects"() {
-        given:
-        def action = Mock(Action)
-
-        substitutions.withModule("org.utils:api", action)
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        0 * _
-    }
-
-    def "eachProject() matches only projects"() {
-        given:
-        def action = Mock(Action)
-        substitutions.eachProject(action)
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        1 * action.execute(projectDetails)
-        0 * _
-
-        def moduleDetails = Mock(ModuleDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
-        0 * _
-    }
-
-    @Unroll
-    def "withProject() matches only given project: #matchingProject"() {
-        given:
-        def matchingProjectAction = Mock(Action)
-        def nonMatchingProjectAction = Mock(Action)
-
-        substitutions.withProject(matchingProject, matchingProjectAction)
-        substitutions.withProject(nonMatchingProject, nonMatchingProjectAction)
-
-        def projectDetails = Mock(ProjectDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(projectDetails)
-
-        then:
-        _ * projectDetails.requested >> DefaultProjectComponentSelector.newSelector(":api")
-        1 * matchingProjectAction.execute(projectDetails)
-        0 * nonMatchingProjectAction.execute(_)
-        0 * _
-
-        where:
-        matchingProject                                             | nonMatchingProject
-        ":api"                                                      | ":impl"
-        Mock(Project) { Project project -> project.path >> ":api" } | Mock(Project) { Project project -> project.path >> ":impl" }
-    }
-
-    def "withProject() does not match modules"() {
-        def action = Mock(Action)
-        substitutions.withProject(":api", action)
-        def moduleDetails = Mock(ModuleDependencySubstitution)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(moduleDetails)
-
-        then:
-        _ * moduleDetails.requested >> DefaultModuleComponentSelector.newSelector("org.utils", "api", "1.5")
-        0 * _
-    }
-
-    def "provides dependency substitution rule that orderly aggregates user specified rules"() {
-        given:
-        substitutions.eachModule({ it.useVersion("1.0") } as Action)
-        substitutions.eachModule({ it.useVersion("2.0") } as Action)
-        substitutions.eachModule({ it.useVersion("3.0") } as Action)
-        def details = Mock(ModuleDependencySubstitutionInternal)
-
-        when:
-        substitutions.dependencySubstitutionRule.execute(details)
-
-        then:
-        1 * details.useVersion("1.0")
-        then:
-        1 * details.useVersion("2.0")
-        then:
-        1 * details.useVersion("3.0")
-        0 * details._
-    }
-    
-    def "mutations trigger lenient validation"() {
-        given:
-        def validator = Mock(MutationValidator)
-        substitutions.beforeChange(validator)
-        
-        when: substitutions.all(Mock(Action))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.all(Mock(Closure))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.eachModule(Mock(Action))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.eachModule(Mock(Closure))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.withModule("org:foo", Mock(Action))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.withModule("org:foo", Mock(Closure))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.eachProject(Mock(Action))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.eachProject(Mock(Closure))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.withProject(":foo", Mock(Action))
-        then: 1 * validator.validateMutation(STRATEGY)
-        
-        when: substitutions.withProject(":foo", Mock(Closure))
-        then: 1 * validator.validateMutation(STRATEGY)
-    }
-
-    def "mutating copy does not trigger original validator"() {
-        given:
-        def validator = Mock(MutationValidator)
-        substitutions.beforeChange(validator)
-        def copy = substitutions.copy()
-
-        when:
-        copy.all(Mock(Action))
-        copy.all(Mock(Closure))
-        copy.eachModule(Mock(Action))
-        copy.eachModule(Mock(Closure))
-        copy.withModule("org:foo", Mock(Action))
-        copy.withModule("org:foo", Mock(Closure))
-        copy.eachProject(Mock(Action))
-        copy.eachProject(Mock(Closure))
-        copy.withProject(":foo", Mock(Action))
-        copy.withProject(":foo", Mock(Closure))
-
-        then:
-        0 * validator.validateMutation(_)
-    }
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
index acc5c9c..ef0f6ab 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
@@ -15,14 +15,15 @@
  */
 
 package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy
-
 import org.gradle.api.Action
 import org.gradle.api.artifacts.ComponentSelection
 import org.gradle.api.artifacts.ComponentSelectionRules
-import org.gradle.api.internal.artifacts.ModuleDependencySubstitutionInternal
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal
 import org.gradle.api.internal.artifacts.configurations.MutationValidator
+import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DependencySubstitutionsInternal
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 import org.gradle.internal.Actions
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
 import org.gradle.internal.rules.NoInputsRuleAction
 import spock.lang.Specification
 
@@ -74,15 +75,16 @@ public class DefaultResolutionStrategySpec extends Specification {
     def "provides dependency resolve rule that forces modules"() {
         given:
         strategy.force 'org:bar:1.0', 'org:foo:2.0'
-        def details = Mock(ModuleDependencySubstitutionInternal)
+        def details = Mock(DependencySubstitutionInternal)
 
         when:
         strategy.dependencySubstitutionRule.execute(details)
 
         then:
         _ * dependencySubstitutions.dependencySubstitutionRule >> Actions.doNothing()
+        _ * details.getRequested() >> DefaultModuleComponentSelector.newSelector("org", "foo", "1.0")
         _ * details.getOldRequested() >> newSelector("org", "foo", "1.0")
-        1 * details.useVersion("2.0", VersionSelectionReasons.FORCED)
+        1 * details.useTarget(DefaultModuleComponentSelector.newSelector("org", "foo", "2.0"), VersionSelectionReasons.FORCED)
         0 * details._
     }
 
@@ -100,7 +102,7 @@ public class DefaultResolutionStrategySpec extends Specification {
     def "provides dependency resolve rule with forced modules first and then user specified rules"() {
         given:
         strategy.force 'org:bar:1.0', 'org:foo:2.0'
-        def details = Mock(ModuleDependencySubstitutionInternal)
+        def details = Mock(DependencySubstitutionInternal)
         def substitutionAction = Mock(Action)
 
         when:
@@ -108,8 +110,9 @@ public class DefaultResolutionStrategySpec extends Specification {
 
         then: //forced modules:
         dependencySubstitutions.dependencySubstitutionRule >> substitutionAction
+        _ * details.requested >> DefaultModuleComponentSelector.newSelector("org", "foo", "1.0")
         _ * details.oldRequested >> newSelector("org", "foo", "1.0")
-        1 * details.useVersion("2.0", VersionSelectionReasons.FORCED)
+        1 * details.useTarget(DefaultModuleComponentSelector.newSelector("org", "foo", "2.0"), VersionSelectionReasons.FORCED)
 
         then: //user rules follow:
         1 * substitutionAction.execute(details)
@@ -187,7 +190,7 @@ public class DefaultResolutionStrategySpec extends Specification {
 
     def "mutation is checked for public API"() {
         def validator = Mock(MutationValidator)
-        strategy.beforeChange(validator)
+        strategy.setMutationValidator(validator)
 
         when: strategy.failOnVersionConflict()
         then: 1 * validator.validateMutation(STRATEGY)
@@ -219,7 +222,7 @@ public class DefaultResolutionStrategySpec extends Specification {
         cachePolicy.copy() >> Mock(DefaultCachePolicy)
         dependencySubstitutions.copy() >> Mock(DependencySubstitutionsInternal)
         def validator = Mock(MutationValidator)
-        strategy.beforeChange(validator)
+        strategy.setMutationValidator(validator)
         def copy = strategy.copy()
 
         when: copy.failOnVersionConflict()
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
index 61cd944..3f10a77 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
@@ -15,9 +15,9 @@
  */
 
 package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy
-
-import org.gradle.api.internal.artifacts.ModuleDependencySubstitutionInternal
+import org.gradle.api.internal.artifacts.DependencySubstitutionInternal
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
@@ -26,7 +26,7 @@ class ModuleForcingResolveRuleSpec extends Specification {
 
     def "forces modules"() {
         given:
-        def details = Mock(ModuleDependencySubstitutionInternal)
+        def details = Mock(DependencySubstitutionInternal)
 
         when:
         new ModuleForcingResolveRule([
@@ -38,8 +38,9 @@ class ModuleForcingResolveRuleSpec extends Specification {
         ]).execute(details)
 
         then:
+        _ * details.requested >> DefaultModuleComponentSelector.newSelector(requested)
         _ * details.getOldRequested() >> requested
-        1 * details.useVersion(forcedVersion, VersionSelectionReasons.FORCED)
+        1 * details.useTarget(DefaultModuleComponentSelector.newSelector(requested.group, requested.name, forcedVersion), VersionSelectionReasons.FORCED)
         0 * details._
 
         where:
@@ -52,7 +53,7 @@ class ModuleForcingResolveRuleSpec extends Specification {
 
     def "does not force modules if they dont match"() {
         given:
-        def details = Mock(ModuleDependencySubstitutionInternal)
+        def details = Mock(DependencySubstitutionInternal)
 
         when:
         new ModuleForcingResolveRule([
@@ -78,7 +79,7 @@ class ModuleForcingResolveRuleSpec extends Specification {
     }
 
     def "does not force anything when input empty"() {
-        def details = Mock(ModuleDependencySubstitutionInternal)
+        def details = Mock(DependencySubstitutionInternal)
 
         when:
         new ModuleForcingResolveRule([]).execute(details)
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilterTest.groovy
index 95b8484..6f8b3c9 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilterTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultModuleResolutionFilterTest.groovy
@@ -37,6 +37,7 @@ class DefaultModuleResolutionFilterTest extends Specification {
 
         expect:
         spec.acceptArtifact(moduleId("org", "module"), artifactName("test", "jar", "jar"))
+        spec.acceptsAllArtifacts()
     }
 
     def "default specs accept the same modules as each other"() {
@@ -114,6 +115,7 @@ class DefaultModuleResolutionFilterTest extends Specification {
 
         then:
         spec.acceptModule(moduleId('org', 'module'))
+        !spec.acceptsAllArtifacts()
 
         where:
         rule << [excludeRule('*', '*', 'artifact'),
@@ -129,6 +131,7 @@ class DefaultModuleResolutionFilterTest extends Specification {
 
         then:
         spec.acceptArtifact(moduleId('org', 'module'), artifactName('name', 'jar', 'jar'))
+        spec.acceptsAllArtifacts()
 
         where:
         rule << [excludeRule('*', '*'),
@@ -149,6 +152,7 @@ class DefaultModuleResolutionFilterTest extends Specification {
 
         then:
         !spec.acceptArtifact(moduleId('org', 'module'), artifactName('mylib', 'jar', 'jar'))
+        !spec.acceptsAllArtifacts()
 
         where:
         rule << [excludeRule('org', 'module', 'mylib', 'jar', 'jar'),
@@ -399,7 +403,7 @@ class DefaultModuleResolutionFilterTest extends Specification {
         union3 == DefaultModuleResolutionFilter.all()
     }
 
-    def "union of two specs with disjoint exact matching exclude rules matches all modules"() {
+    def "union of two specs with disjoint exact matching exclude rules excludes no modules"() {
         def rule1 = excludeRule("org", "module")
         def rule2 = excludeRule("org", "module2")
         def spec = DefaultModuleResolutionFilter.excludeAny(rule1)
@@ -410,6 +414,17 @@ class DefaultModuleResolutionFilterTest extends Specification {
         union == DefaultModuleResolutionFilter.all()
     }
 
+    def "union of a spec with exclude-all spec returns the original spec"() {
+        def rule1 = excludeRule("*", "*")
+        def rule2 = excludeRule("org", "module2")
+        def spec1 = DefaultModuleResolutionFilter.excludeAny(rule1)
+        def spec2 = DefaultModuleResolutionFilter.excludeAny(rule2)
+
+        expect:
+        spec1.union(spec2) == spec2
+        spec2.union(spec1) == spec2
+    }
+
     def "union of module spec and artifact spec uses the artifact spec"() {
         def rule1 = excludeRule("org", "module")
         def rule2 = excludeRule("*", "module-2")
@@ -480,6 +495,24 @@ class DefaultModuleResolutionFilterTest extends Specification {
         union.acceptModule(moduleId("org", "module2"))
     }
 
+    def "union accepts artifact that is accepted by any merged exclude rule"() {
+        def moduleId = moduleId("org", "module")
+        def excludeA = excludeRule("org", "module", "a")
+        def excludeB = excludeRule("org", "module", "b")
+        def spec = DefaultModuleResolutionFilter.excludeAny(excludeA)
+        def spec2 = DefaultModuleResolutionFilter.excludeAny(excludeB)
+
+        when:
+        def union = spec.union(spec2)
+
+        then:
+        !union.acceptArtifact(moduleId, artifactName("a", "zip", "zip"))
+        union.acceptArtifact(moduleId, artifactName("b", "zip", "zip"))
+        union.acceptArtifact(moduleId, artifactName("c", "zip", "zip"))
+
+        !union.acceptsAllArtifacts()
+    }
+
     def "unions accepts same modules when original specs accept same modules"() {
         def rule1 = regexpExcludeRule("org", "module")
         def rule2 = regexpExcludeRule("org", "module2")
@@ -537,6 +570,23 @@ class DefaultModuleResolutionFilterTest extends Specification {
         intersect.acceptModule(moduleId("org", "module3"))
     }
 
+    def "intersection accepts artifact that is accepted by every merged exclude rule"() {
+        def moduleId = moduleId("org", "module")
+        def excludeA = excludeRule("org", "module", "a")
+        def excludeB = excludeRule("org", "module", "b")
+        def spec = DefaultModuleResolutionFilter.excludeAny(excludeA, excludeB)
+        def spec2 = DefaultModuleResolutionFilter.excludeAny(excludeA)
+
+        expect:
+        def intersect = spec.intersect(spec2)
+
+        !intersect.acceptArtifact(moduleId, artifactName("a", "zip", "zip"))
+        !intersect.acceptArtifact(moduleId, artifactName("b", "zip", "zip"))
+        intersect.acceptArtifact(moduleId, artifactName("c", "zip", "zip"))
+
+        !intersect.acceptsAllArtifacts()
+    }
+
     def "intersection of two specs with exclude rules is the union of the exclude rules"() {
         def rule1 = excludeRule("org", "module")
         def rule2 = excludeRule("org", "module2")
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
index 1dcdc07..e85bf2c 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
@@ -30,8 +30,10 @@ import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
 import org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.DependencyGraphBuilder
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.DefaultConflictHandler
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.DefaultResolvedArtifactsBuilder
 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.oldresult.TransientConfigurationResultsLoader
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfigurationResultBuilder
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DummyBinaryStore
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DummyStore
@@ -47,7 +49,7 @@ import org.gradle.internal.resolve.ModuleVersionResolveException
 import org.gradle.internal.resolve.resolver.ArtifactResolver
 import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver
 import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver
-import org.gradle.internal.resolve.resolver.ModuleToComponentResolver
+import org.gradle.internal.resolve.resolver.ResolveContextToComponentResolver
 import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult
 import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult
@@ -63,10 +65,10 @@ class DependencyGraphBuilderTest extends Specification {
     def idResolver = Mock(DependencyToComponentIdResolver)
     def metaDataResolver = Mock(ComponentMetaDataResolver)
     def artifactResolver = Mock(ArtifactResolver)
-    def resultBuilder = Mock(ResolutionResultBuilder)
+    def resolutionResultBuilder = Mock(ResolutionResultBuilder)
     def projectModelBuilder = Mock(ResolvedProjectConfigurationResultBuilder)
     def TestMetaData root = project('root')
-    def moduleResolver = Mock(ModuleToComponentResolver)
+    def moduleResolver = Mock(ResolveContextToComponentResolver)
     def dependencyToConfigurationResolver = new DefaultDependencyToConfigurationResolver()
     def moduleReplacements = Mock(ModuleReplacementsData)
     def builder = new DependencyGraphBuilder(idResolver, metaDataResolver, moduleResolver, artifactResolver, new DefaultConflictHandler(conflictResolver, moduleReplacements), dependencyToConfigurationResolver)
@@ -75,17 +77,24 @@ class DependencyGraphBuilderTest extends Specification {
         config(root, 'root', 'default')
         _ * configuration.name >> 'root'
         _ * configuration.path >> 'root'
-        _ * moduleResolver.resolve(_, _, _) >> { it[2].resolved(root) }
+        _ * moduleResolver.resolve(_, _) >> { it[1].resolved(root) }
 
         _ * artifactResolver.resolveModuleArtifacts(_, _, _,) >> { ComponentResolveMetaData module, ComponentUsage context, BuildableArtifactSetResolveResult result ->
-            result.resolved(module.artifacts)
+            result.resolved(module.getConfiguration(context.configurationName).artifacts)
         }
     }
 
     private DefaultLenientConfiguration resolve() {
-        def results = new DefaultResolvedConfigurationBuilder(new TransientConfigurationResultsBuilder(new DummyBinaryStore(), new DummyStore()))
-        builder.resolve(configuration, resultBuilder, results, projectModelBuilder)
-        new DefaultLenientConfiguration(configuration, results, Stub(CacheLockingManager))
+        def transientConfigurationResultsBuilder = new TransientConfigurationResultsBuilder(new DummyBinaryStore(), new DummyStore())
+        def modelBuilder = new DefaultResolvedConfigurationBuilder(transientConfigurationResultsBuilder)
+        def artifactsBuilder = new DefaultResolvedArtifactsBuilder()
+
+        builder.resolve(configuration, resolutionResultBuilder, modelBuilder, artifactsBuilder, projectModelBuilder)
+
+        def graphResults = modelBuilder.complete()
+        def artifactResults = artifactsBuilder.resolve()
+
+        new DefaultLenientConfiguration(configuration, Stub(CacheLockingManager), graphResults, artifactResults, new TransientConfigurationResultsLoader(transientConfigurationResultsBuilder, graphResults, artifactResults))
     }
 
     def "does not resolve a given module selector more than once"() {
@@ -121,11 +130,11 @@ class DependencyGraphBuilderTest extends Specification {
         resolve()
 
         then:
-        1 * resultBuilder.start(newId("group", "root", "1.0"), new DefaultProjectComponentIdentifier(":root"))
+        1 * resolutionResultBuilder.start(newId("group", "root", "1.0"), new DefaultProjectComponentIdentifier(":root"))
         then:
-        1 * resultBuilder.resolvedConfiguration({ it.name == 'root' }, { it*.requested.module == ['a', 'b'] })
+        1 * resolutionResultBuilder.resolvedConfiguration({ it.name == 'root' }, { it*.requested.module == ['a', 'b'] })
         then:
-        1 * resultBuilder.resolvedConfiguration({ it.name == 'a' }, { it*.requested.module == ['c', 'd'] && it*.failure.count { it != null } == 1 })
+        1 * resolutionResultBuilder.resolvedConfiguration({ it.name == 'a' }, { it*.requested.module == ['c', 'd'] && it*.failure.count { it != null } == 1 })
     }
 
     def "correctly notifies the project configuration result builder"() {
@@ -941,7 +950,8 @@ class DependencyGraphBuilderTest extends Specification {
     def traverses(Map<String, ?> args = [:], TestMetaData from, ComponentResolveMetaData to) {
         def dependencyMetaData = dependsOn(args, from, to.descriptor.moduleRevisionId)
         selectorResolvesTo(dependencyMetaData, to.componentId, to.id)
-        1 * metaDataResolver.resolve(dependencyMetaData, to.componentId, _) >> { DependencyMetaData dep, ComponentIdentifier id, BuildableComponentResolveResult result ->
+
+        1 * metaDataResolver.resolve(to.componentId, _, _) >> { ComponentIdentifier id, ComponentOverrideMetadata requestMetaData, BuildableComponentResolveResult result ->
             result.resolved(to)
         }
     }
@@ -949,27 +959,27 @@ class DependencyGraphBuilderTest extends Specification {
     def doesNotTraverse(Map<String, ?> args = [:], TestMetaData from, ComponentResolveMetaData to) {
         def dependencyMetaData = dependsOn(args, from, to.descriptor.moduleRevisionId)
         selectorResolvesTo(dependencyMetaData, to.componentId, to.id)
-        0 * metaDataResolver.resolve(_, to.componentId, _)
+        0 * metaDataResolver.resolve(to.componentId, _, _)
     }
 
     def doesNotResolve(Map<String, ?> args = [:], TestMetaData from, ComponentResolveMetaData to) {
         def dependencyMetaData = dependsOn(args, from, to.descriptor.moduleRevisionId)
         0 * idResolver.resolve(dependencyMetaData, _)
-        0 * metaDataResolver.resolve(_, to.componentId, _)
+        0 * metaDataResolver.resolve(to.componentId, _, _)
     }
 
     def traversesMissing(Map<String, ?> args = [:], TestMetaData from, ComponentResolveMetaData to) {
         def dependencyMetaData = dependsOn(args, from, to.descriptor.moduleRevisionId)
         selectorResolvesTo(dependencyMetaData, to.componentId, to.id)
-        1 * metaDataResolver.resolve(_, to.componentId, _) >> { DependencyMetaData dep, ComponentIdentifier id, BuildableComponentResolveResult result ->
-            result.notFound(to.id)
+        1 * metaDataResolver.resolve(to.componentId, _, _) >> { ComponentIdentifier id, ComponentOverrideMetadata requestMetaData, BuildableComponentResolveResult result ->
+            result.notFound(to.componentId)
         }
     }
 
     def traversesBroken(Map<String, ?> args = [:], TestMetaData from, ComponentResolveMetaData to) {
         def dependencyMetaData = dependsOn(args, from, to.descriptor.moduleRevisionId)
         selectorResolvesTo(dependencyMetaData, to.componentId, to.id)
-        1 * metaDataResolver.resolve(_, to.componentId, _) >> { DependencyMetaData dep, ComponentIdentifier id, BuildableComponentResolveResult result ->
+        1 * metaDataResolver.resolve(to.componentId, _, _) >> { ComponentIdentifier id, ComponentOverrideMetadata requestMetaData, BuildableComponentResolveResult result ->
             result.failed(new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken"))
         }
     }
@@ -1081,15 +1091,5 @@ class DependencyGraphBuilderTest extends Specification {
         ComponentResolveMetaData withSource(ModuleSource source) {
             return null
         }
-
-        @Override
-        ComponentArtifactMetaData artifact(Artifact artifact) {
-            return null
-        }
-
-        @Override
-        Set<? extends ComponentArtifactMetaData> getArtifacts() {
-            return []
-        }
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy
index bb068c4..8403cd8 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentIdentifierSerializerTest.groovy
@@ -16,9 +16,11 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result
 
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
+import org.gradle.internal.component.local.model.DefaultLibraryComponentIdentifier
 import org.gradle.internal.component.local.model.DefaultProjectComponentIdentifier
 import org.gradle.internal.serialize.SerializerSpec
 
@@ -47,6 +49,18 @@ class ComponentIdentifierSerializerTest extends SerializerSpec {
         result.version == 'version-one'
     }
 
+    def "serializes LibraryIdentifier"() {
+        given:
+        LibraryComponentIdentifier selection = new DefaultLibraryComponentIdentifier(':project', 'lib')
+
+        when:
+        LibraryComponentIdentifier result = serialize(selection, serializer)
+
+        then:
+        result.projectPath == ':project'
+        result.libraryName == 'lib'
+    }
+
     def "serializes BuildComponentIdentifier"() {
         given:
         ProjectComponentIdentifier selection = new DefaultProjectComponentIdentifier(':myPath')
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy
index 4ee1d62..f50ee77 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectorSerializerTest.groovy
@@ -16,10 +16,12 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result
 
-import org.gradle.api.artifacts.component.ProjectComponentSelector
+import org.gradle.api.artifacts.component.LibraryComponentSelector
 import org.gradle.api.artifacts.component.ModuleComponentSelector
-import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
+import org.gradle.api.artifacts.component.ProjectComponentSelector
 import org.gradle.internal.component.external.model.DefaultModuleComponentSelector
+import org.gradle.internal.component.local.model.DefaultLibraryComponentSelector
+import org.gradle.internal.component.local.model.DefaultProjectComponentSelector
 import org.gradle.internal.serialize.SerializerSpec
 
 public class ComponentSelectorSerializerTest extends SerializerSpec {
@@ -57,4 +59,16 @@ public class ComponentSelectorSerializerTest extends SerializerSpec {
         then:
         result.projectPath == ':myPath'
     }
+
+    def "serializes LibraryComponentSelector"() {
+        given:
+        LibraryComponentSelector selection = new DefaultLibraryComponentSelector(':myPath', 'myLib')
+
+        when:
+        LibraryComponentSelector result = serialize(selection, serializer)
+
+        then:
+        result.projectPath == ':myPath'
+        result.libraryName == 'myLib'
+    }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy
index c12aead..279fe60 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/store/DefaultBinaryStoreTest.groovy
@@ -67,6 +67,7 @@ class DefaultBinaryStoreTest extends Specification {
 
         cleanup:
         store.close()
+
     }
 
     class SomeException extends RuntimeException {}
@@ -80,6 +81,9 @@ class DefaultBinaryStoreTest extends Specification {
         then:
         def e = thrown(Exception)
         e.cause.class == SomeException
+
+        cleanup:
+        store.close()
     }
 
     def "read action exception is propagated to the client"() {
@@ -93,6 +97,10 @@ class DefaultBinaryStoreTest extends Specification {
         then:
         def e = thrown(Exception)
         e.cause.class == SomeException
+
+        cleanup:
+        data.close()
+        store.close()
     }
 
     def "may be empty"() {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQueryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQueryTest.groovy
index 4a0a9a3..684d683 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQueryTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/query/DefaultArtifactResolutionQueryTest.groovy
@@ -15,7 +15,7 @@
  */
 
 package org.gradle.api.internal.artifacts.query
-
+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.result.ArtifactResolutionResult
@@ -31,17 +31,17 @@ import org.gradle.api.internal.component.ComponentTypeRegistration
 import org.gradle.api.internal.component.ComponentTypeRegistry
 import org.gradle.internal.Factory
 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
+import org.gradle.internal.component.model.ComponentOverrideMetadata
 import org.gradle.internal.component.model.ComponentResolveMetaData
-import org.gradle.internal.component.model.DependencyMetaData
 import org.gradle.internal.resolve.resolver.ArtifactResolver
-import org.gradle.internal.resolve.resolver.DependencyToComponentResolver
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver
 import org.gradle.internal.resolve.result.BuildableComponentResolveResult
 import spock.lang.Shared
 import spock.lang.Specification
 import spock.lang.Unroll
 
 class DefaultArtifactResolutionQueryTest extends Specification {
-    def configurationContainerInternal = Mock(ConfigurationContainerInternal)
+    def configurationContainerInternal = Stub(ConfigurationContainerInternal)
     def repositoryHandler = Stub(RepositoryHandler)
     def resolveIvyFactory = Mock(ResolveIvyFactory)
     def globalDependencyResolutionRules = Mock(GlobalDependencyResolutionRules)
@@ -49,7 +49,7 @@ class DefaultArtifactResolutionQueryTest extends Specification {
     def componentTypeRegistry = Mock(ComponentTypeRegistry)
     def artifactResolver = Mock(ArtifactResolver)
     def repositoryChain = Mock(RepositoryChain)
-    def dependencyToComponentResolver = Mock(DependencyToComponentResolver)
+    def componentMetaDataResolver = Mock(ComponentMetaDataResolver)
     def componentResolveMetaData = Mock(ComponentResolveMetaData)
 
     @Shared ComponentTypeRegistry testComponentTypeRegistry = createTestComponentTypeRegistry()
@@ -93,8 +93,8 @@ class DefaultArtifactResolutionQueryTest extends Specification {
         }
         1 * resolveIvyFactory.create(_, _, _) >> repositoryChain
         1 * repositoryChain.artifactResolver >> artifactResolver
-        1 * repositoryChain.dependencyResolver >> dependencyToComponentResolver
-        1 * dependencyToComponentResolver.resolve(_, _) >> { DependencyMetaData dependency, BuildableComponentResolveResult resolveResult ->
+        1 * repositoryChain.componentResolver >> componentMetaDataResolver
+        1 * componentMetaDataResolver.resolve(_, _, _) >> { ComponentIdentifier componentId, ComponentOverrideMetadata requestMetaData, BuildableComponentResolveResult resolveResult ->
             resolveResult.resolved(componentResolveMetaData)
         }
         result
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationConverterTest.groovy
index 27048e0..f19a238 100755
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationConverterTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationConverterTest.groovy
@@ -17,10 +17,10 @@ package org.gradle.api.internal.notations
 
 import org.gradle.api.artifacts.Dependency
 import org.gradle.api.artifacts.SelfResolvingDependency
-import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.api.internal.file.FileCollectionInternal
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.internal.classpath.ClassPath
 import org.gradle.internal.reflect.Instantiator
@@ -36,7 +36,7 @@ public class DependencyClassPathNotationConverterTest extends Specification {
     def "parses classpath literals"() {
         given:
         def dependency = Mock(SelfResolvingDependency.class)
-        def fileCollection = Mock(FileCollection.class)
+        def fileCollection = Mock(FileCollectionInternal)
         def classpath = Mock(ClassPath.class)
         def files = [new File('foo')]
 
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentiferNotationConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentiferNotationConverterTest.groovy
deleted file mode 100644
index d743cf8..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentiferNotationConverterTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.notations
-
-import org.gradle.api.artifacts.ModuleIdentifier
-import org.gradle.internal.typeconversion.NotationParserBuilder
-import org.gradle.internal.typeconversion.UnsupportedNotationException
-import spock.lang.Specification
-import spock.lang.Subject
-
-import static org.gradle.api.internal.artifacts.DefaultModuleIdentifier.newId
-
-class ModuleIdentiferNotationConverterTest extends Specification {
-
-    @Subject parser = NotationParserBuilder.toType(ModuleIdentifier).converter(new ModuleIdentiferNotationConverter()).toComposite()
-
-    def "parses module identifer notation"() {
-        expect:
-        parser.parseNotation("org.gradle:gradle-core") == newId("org.gradle", "gradle-core")
-        parser.parseNotation(" foo:bar ") == newId("foo", "bar")
-    }
-
-    def "reports invalid notation"() {
-        when: parser.parseNotation(notation)
-        then: thrown(UnsupportedNotationException)
-        where: notation << [null, "", ":", "foo:", "bar:", "foo:bar:baz", "  :", ":  ", "  :  "]
-    }
-
-    def "reports notation with invalid character"() {
-        when: parser.parseNotation("group:module${character}")
-        then: thrown(UnsupportedNotationException)
-        where: character << ["+", "*", "[", "]", "(", ")", ","]
-    }
-
-
-}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentifierNotationConverterTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentifierNotationConverterTest.groovy
new file mode 100644
index 0000000..de11400
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/notations/ModuleIdentifierNotationConverterTest.groovy
@@ -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.api.internal.notations
+
+import org.gradle.api.artifacts.ModuleIdentifier
+import org.gradle.internal.typeconversion.NotationParserBuilder
+import org.gradle.internal.typeconversion.UnsupportedNotationException
+import spock.lang.Specification
+import spock.lang.Subject
+
+import static org.gradle.api.internal.artifacts.DefaultModuleIdentifier.newId
+
+class ModuleIdentifierNotationConverterTest extends Specification {
+
+    @Subject parser = NotationParserBuilder.toType(ModuleIdentifier).converter(new ModuleIdentifierNotationConverter()).toComposite()
+
+    def "parses module identifer notation"() {
+        expect:
+        parser.parseNotation("org.gradle:gradle-core") == newId("org.gradle", "gradle-core")
+        parser.parseNotation(" foo:bar ") == newId("foo", "bar")
+    }
+
+    def "reports invalid notation"() {
+        when: parser.parseNotation(notation)
+        then: thrown(UnsupportedNotationException)
+        where: notation << [null, "", ":", "foo:", "bar:", "foo:bar:baz", "  :", ":  ", "  :  "]
+    }
+
+    def "reports notation with invalid character"() {
+        when: parser.parseNotation("group:module${character}")
+        then: thrown(UnsupportedNotationException)
+        where: character << ["+", "*", "[", "]", "(", ")", ","]
+    }
+
+
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaDataTest.groovy
index 7bfdf9d..ede4ff2 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaDataTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/AbstractModuleComponentResolveMetaDataTest.groovy
@@ -145,25 +145,6 @@ abstract class AbstractModuleComponentResolveMetaDataTest extends Specification
         metaData.getConfiguration("conf").dependencies == []
     }
 
-    def "builds and caches artifacts from the module descriptor"() {
-        def artifact1 = artifact("one")
-        def artifact2 = artifact("two")
-
-        given:
-        moduleDescriptor.allArtifacts >> ([artifact1, artifact2] as Artifact[])
-        moduleDescriptor.configurationsNames >> ["conf1"]
-        moduleDescriptor.getArtifacts("conf1") >> ([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
@@ -189,25 +170,11 @@ abstract class AbstractModuleComponentResolveMetaDataTest extends Specification
 
         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)
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaDataTest.groovy
index 9bcb0df..f0d92f7 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaDataTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultIvyModulePublishMetaDataTest.groovy
@@ -15,13 +15,16 @@
  */
 
 package org.gradle.internal.component.external.model
-
 import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.descriptor.Configuration
 import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.internal.component.local.model.LocalConfigurationMetaData
+import org.gradle.internal.component.model.DependencyMetaData
 import spock.lang.Specification
 
 class DefaultIvyModulePublishMetaDataTest extends Specification {
-    def metaData = new DefaultIvyModulePublishMetaData(Stub(ModuleVersionIdentifier))
+    def metaData = new DefaultIvyModulePublishMetaData(Stub(ModuleVersionIdentifier), "status")
 
     def "can add artifacts"() {
         def artifact = Stub(Artifact)
@@ -35,8 +38,59 @@ class DefaultIvyModulePublishMetaDataTest extends Specification {
         def publishArtifact = metaData.artifacts.iterator().next()
         publishArtifact.artifact == artifact
         publishArtifact.file == file
+    }
+
+    def "can add configuration"() {
+        def configuration = mockConfiguration()
+
+        when:
+        metaData.addConfiguration(configuration)
+
+        then:
+        metaData.moduleDescriptor.configurations.length == 1
+        Configuration conf = metaData.moduleDescriptor.configurations[0]
+        conf.name == "configName"
+        conf.description == "configDescription"
+        conf.extends as List == ["one", "three", "two"]
+        conf.visibility == Configuration.Visibility.PUBLIC
+        conf.transitive
+    }
+
+    def mockConfiguration() {
+        return Stub(LocalConfigurationMetaData) { configuration ->
+            configuration.name >> "configName"
+            configuration.description >> "configDescription"
+            configuration.extendsFrom >> (["one", "two", "three"] as Set)
+            configuration.visible >> true
+            configuration.transitive >> true
+        }
+    }
+
+    def "can add dependencies"() {
+        def dependency = Mock(DependencyMetaData)
+
+        given:
+        metaData.addConfiguration(mockConfiguration())
 
         and:
-        metaData.getArtifact(publishArtifact.id) == publishArtifact
+        dependency.requested >> DefaultModuleVersionSelector.newSelector("group", "module", "version")
+        dependency.force >> true
+        dependency.changing >> true
+        dependency.transitive >> true
+        dependency.moduleConfigurations >> (["configName"] as String[])
+        dependency.getDependencyConfigurations("configName", "configName") >> (["dep1"] as String[])
+        dependency.artifacts >> ([] as Set)
+
+        when:
+        metaData.addDependency(dependency)
+
+        then:
+        metaData.moduleDescriptor.dependencies.length == 1
+        def depDescriptor = metaData.moduleDescriptor.dependencies[0]
+        depDescriptor.force
+        depDescriptor.changing
+        depDescriptor.transitive
+        depDescriptor.moduleConfigurations as List == ["configName"]
+        depDescriptor.allDependencyArtifacts.length == 0
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaDataTest.groovy
index 1bd56ce..264fc72 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaDataTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultModuleComponentArtifactMetaDataTest.groovy
@@ -15,8 +15,9 @@
  */
 
 package org.gradle.internal.component.external.model
-import org.apache.ivy.core.module.descriptor.Artifact
+
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.internal.component.model.DefaultIvyArtifactName
 import spock.lang.Specification
 
 class DefaultModuleComponentArtifactMetaDataTest extends Specification {
@@ -49,23 +50,7 @@ class DefaultModuleComponentArtifactMetaDataTest extends Specification {
         noExtension.name.classifier == null
     }
 
-    def "converts to Ivy artifact"() {
-        expect:
-        def original = ivyArtifact("name", "type", "ext", ['classifier': 'classifier'])
-        def artifact = new DefaultModuleComponentArtifactMetaData(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
+        new DefaultIvyArtifactName(name, type, extension, attributes)
     }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifierTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifierTest.groovy
new file mode 100644
index 0000000..c90b34e
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentIdentifierTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.component.local.model
+
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultLibraryComponentIdentifierTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        LibraryComponentIdentifier defaultBuildComponentIdentifier = new DefaultLibraryComponentIdentifier(':myPath', 'myLib')
+
+        then:
+        defaultBuildComponentIdentifier.projectPath == ':myPath'
+        defaultBuildComponentIdentifier.libraryName == 'myLib'
+        defaultBuildComponentIdentifier.displayName == 'project :myPath library myLib'
+        defaultBuildComponentIdentifier.toString() == 'project :myPath library myLib'
+    }
+
+    def "is instantiated with null project constructor parameter value"() {
+        when:
+        new DefaultLibraryComponentIdentifier(null, 'foo')
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'project path cannot be null'
+    }
+
+    def "is instantiated with null library constructor parameter value"() {
+        when:
+        new DefaultLibraryComponentIdentifier('foo', null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'library name cannot be null'
+    }
+
+    @Unroll
+    def "can compare with other instance (#projectPath,#libraryName)"() {
+        expect:
+        LibraryComponentIdentifier defaultBuildComponentIdentifier1 = new DefaultLibraryComponentIdentifier(':myProjectPath1', 'myLib')
+        LibraryComponentIdentifier defaultBuildComponentIdentifier2 = new DefaultLibraryComponentIdentifier(projectPath, libraryName)
+        strictlyEquals(defaultBuildComponentIdentifier1, defaultBuildComponentIdentifier2) == equality
+        (defaultBuildComponentIdentifier1.hashCode() == defaultBuildComponentIdentifier2.hashCode()) == hashCode
+        (defaultBuildComponentIdentifier1.toString() == defaultBuildComponentIdentifier2.toString()) == stringRepresentation
+
+        where:
+        projectPath       | libraryName | equality | hashCode | stringRepresentation
+        ':myProjectPath1' | 'myLib'     | true     | true     | true
+        ':myProjectPath2' | 'myLib'     | false    | false    | false
+        ':myProjectPath1' | 'myLib2'    | false    | false    | false
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentSelectorTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentSelectorTest.groovy
new file mode 100644
index 0000000..a9a0971
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLibraryComponentSelectorTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.component.local.model
+
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier
+import org.gradle.api.artifacts.component.LibraryComponentSelector
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.Matchers.strictlyEquals
+
+class DefaultLibraryComponentSelectorTest extends Specification {
+    def "is instantiated with non-null constructor parameter values"() {
+        when:
+        LibraryComponentSelector defaultBuildComponentSelector = new DefaultLibraryComponentSelector(':myPath', 'myLib')
+
+        then:
+        defaultBuildComponentSelector.projectPath == ':myPath'
+        defaultBuildComponentSelector.libraryName == 'myLib'
+        defaultBuildComponentSelector.displayName == 'project :myPath library myLib'
+        defaultBuildComponentSelector.toString() == 'project :myPath library myLib'
+    }
+
+    def "is instantiated with null project constructor parameter value"() {
+        when:
+        new DefaultLibraryComponentSelector(null, 'foo')
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'project path cannot be null'
+    }
+
+    def "is instantiated with null library constructor parameter value"() {
+        when:
+        new DefaultLibraryComponentSelector(':foo', null)
+
+        then:
+        Throwable t = thrown(AssertionError)
+        t.message == 'library name cannot be null'
+    }
+
+    @Unroll
+    def "can compare with other instance (#projectPath,#libraryName)"() {
+        expect:
+        LibraryComponentSelector defaultBuildComponentSelector1 = new DefaultLibraryComponentSelector(':myProjectPath1', 'myLib1')
+        LibraryComponentSelector defaultBuildComponentSelector2 = new DefaultLibraryComponentSelector(projectPath, libraryName)
+        strictlyEquals(defaultBuildComponentSelector1, defaultBuildComponentSelector2) == equality
+        (defaultBuildComponentSelector1.hashCode() == defaultBuildComponentSelector2.hashCode()) == hashCode
+        (defaultBuildComponentSelector1.toString() == defaultBuildComponentSelector2.toString()) == stringRepresentation
+
+        where:
+        projectPath       | libraryName | equality | hashCode | stringRepresentation
+        ':myProjectPath1' | 'myLib1'    | true     | true     | true
+        ':myProjectPath1' | 'myLib2'    | false    | false    | false
+        ':myProjectPath2' | 'myLib1'    | false    | false    | false
+    }
+
+    def "prevents matching of null id"() {
+        when:
+        LibraryComponentSelector defaultBuildComponentSelector = new DefaultLibraryComponentSelector(':myPath', 'myLib')
+        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:
+        LibraryComponentSelector defaultBuildComponentSelector = new DefaultLibraryComponentSelector(':myPath', 'myLib')
+        boolean matches = defaultBuildComponentSelector.matchesStrictly(new DefaultModuleComponentIdentifier('group', 'name', '1.0'))
+
+        then:
+        assert !matches
+    }
+
+    @Unroll
+    def "matches id (#projectPath,#libraryName)"() {
+        expect:
+        LibraryComponentSelector defaultBuildComponentSelector = new DefaultLibraryComponentSelector(':myProjectPath1', 'myLib')
+        LibraryComponentIdentifier defaultBuildComponentIdentifier = new DefaultLibraryComponentIdentifier(projectPath, libraryName)
+        defaultBuildComponentSelector.matchesStrictly(defaultBuildComponentIdentifier) == matchesId
+
+        where:
+        projectPath       | libraryName | matchesId
+        ':myProjectPath1' | 'myLib'     | true
+        ':myProjectPath2' | 'myLib'     | false
+        ':myProjectPath1' | 'myLib2'    | false
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifierTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifierTest.groovy
deleted file mode 100644
index 677712f..0000000
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalArtifactIdentifierTest.groovy
+++ /dev/null
@@ -1,67 +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.internal.component.local.model
-
-import org.gradle.api.artifacts.component.ComponentIdentifier
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-import org.gradle.internal.component.external.model.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 == "name.ext (<comp>)"
-        noClassifier.toString() == "name.ext (<comp>)"
-
-        def withClassifier = new DefaultLocalArtifactIdentifier(componentId, "<comp>", "name", "type", "ext", ['classifier': 'classifier'])
-        withClassifier.displayName == "name-classifier.ext (<comp>)"
-        withClassifier.toString() == "name-classifier.ext (<comp>)"
-
-        def noExtension = new DefaultLocalArtifactIdentifier(componentId, "<comp>", "name", "type", null, ['classifier': 'classifier'])
-        noExtension.displayName == "name-classifier (<comp>)"
-        noExtension.toString() == "name-classifier (<comp>)"
-    }
-
-    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/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalComponentMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalComponentMetaDataTest.groovy
index 680a1d6..81f2d75 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalComponentMetaDataTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/DefaultLocalComponentMetaDataTest.groovy
@@ -15,29 +15,51 @@
  */
 
 package org.gradle.internal.component.local.model
-
 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.DependencyDescriptor
-import org.gradle.api.artifacts.component.ComponentIdentifier
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
 import org.gradle.internal.component.model.DefaultIvyArtifactName
 import org.gradle.internal.component.model.DependencyMetaData
+import org.gradle.internal.component.model.IvyArtifactName
+import org.gradle.util.WrapUtil
 import spock.lang.Specification
 
 class DefaultLocalComponentMetaDataTest extends Specification {
-    def moduleDescriptor = new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId("group", "module", "version"), "status", null)
-    def componentIdentifier = Mock(ComponentIdentifier)
-    def metaData = new DefaultLocalComponentMetaData(moduleDescriptor, componentIdentifier)
+    def id = DefaultModuleVersionIdentifier.newId("group", "module", "version")
+    def componentIdentifier = DefaultModuleComponentIdentifier.newId(id)
+    def metaData = new DefaultLocalComponentMetaData(id, componentIdentifier, "status")
 
     def "can lookup configuration after it has been added"() {
         when:
-        metaData.addConfiguration("conf", true, "description", ["super"] as String[], true)
+        metaData.addConfiguration("super", "description", [] as Set, ["super"] as Set, false, false)
+        metaData.addConfiguration("conf", "description", ["super"] as Set, ["super", "conf"] as Set, true, true)
 
         then:
-        metaData.moduleDescriptor.configurations.length == 1
-        metaData.moduleDescriptor.getConfiguration("conf") != null
+        def resolveMetaData = metaData.toResolveMetaData()
+        resolveMetaData.configurationNames == ['conf', 'super'] as Set
+
+        def conf = resolveMetaData.getConfiguration('conf')
+        conf != null
+        conf.visible
+        conf.transitive
+
+        def superConf = resolveMetaData.getConfiguration('super')
+        superConf != null
+        !superConf.visible
+        !superConf.transitive
+
+        and:
+        def publishMetaData = metaData.toPublishMetaData()
+        publishMetaData.getModuleDescriptor().configurations.length == 2
+        publishMetaData.getModuleDescriptor().getConfiguration('conf') != null
+
+        def ivyConf = publishMetaData.getModuleDescriptor().getConfiguration('conf')
+        ivyConf != null
+        ivyConf.transitive
+        ivyConf.visibility == Configuration.Visibility.PUBLIC
     }
 
     def "can lookup artifact in various ways after it has been added"() {
@@ -45,40 +67,46 @@ class DefaultLocalComponentMetaDataTest extends Specification {
         def file = new File("artifact.zip")
 
         given:
-        moduleDescriptor.addConfiguration(new Configuration("conf"))
+        addConfiguration("conf")
 
         when:
-        metaData.addArtifact("conf", artifact, file)
+        addArtifact("conf", artifact, file)
 
         then:
-        metaData.artifacts.size() == 1
-        def publishArtifact = (metaData.artifacts as List).first()
+        def resolveMetaData = metaData.toResolveMetaData()
+        resolveMetaData.getConfiguration("conf").artifacts.size() == 1
+
+        def publishArtifact = resolveMetaData.getConfiguration("conf").artifacts.first()
         publishArtifact.id
         publishArtifact.name.name == artifact.name
         publishArtifact.name.type == artifact.type
         publishArtifact.name.extension == artifact.extension
         publishArtifact.file == file
+        publishArtifact == resolveMetaData.getConfiguration("conf").artifact(artifact)
 
         and:
-        metaData.getArtifact(publishArtifact.id) == publishArtifact
+        def publishMetaData = metaData.toPublishMetaData()
+        publishMetaData.artifacts.size() == 1
 
-        and:
-        def resolveMetaData = metaData.toResolveMetaData()
-        resolveMetaData.artifacts.size() == 1
-        def resolveArtifact = (resolveMetaData.artifacts as List).first()
-        resolveArtifact.id
-        resolveArtifact.componentId == resolveMetaData.componentId
-        resolveArtifact.name.name == artifact.name
-        resolveArtifact.name.type == artifact.type
-        resolveArtifact.name.extension == artifact.extension
+        def publishMetaDataArtifact = (publishMetaData.artifacts as List).first()
+        publishMetaDataArtifact.id
+        publishMetaDataArtifact.id.componentIdentifier == componentIdentifier
+        publishMetaDataArtifact.artifactName.name == artifact.name
+        publishMetaDataArtifact.artifactName.type == artifact.type
+        publishMetaDataArtifact.artifactName.extension == artifact.extension
+    }
 
-        and:
-        moduleDescriptor.getArtifacts("conf").size() == 1
-        def ivyArtifact = (moduleDescriptor.getArtifacts("conf") as List).first()
-        ivyArtifact.name == artifact.name
-        ivyArtifact.type == artifact.type
-        ivyArtifact.ext == artifact.extension
-        ivyArtifact.configurations == ["conf"]
+    private addConfiguration(String name) {
+        metaData.addConfiguration(name, "", [] as Set, [name] as Set, true, true)
+    }
+
+    def addArtifact(String configuration, IvyArtifactName name, File file) {
+        PublishArtifact publishArtifact = new DefaultPublishArtifact(name.name, name.extension, name.type, name.classifier, new Date(), file)
+        addArtifact(configuration, publishArtifact)
+    }
+
+    def addArtifact(String configuration, PublishArtifact publishArtifact) {
+        metaData.addArtifacts(configuration, new DefaultPublishArtifactSet("arts", WrapUtil.toDomainObjectSet(PublishArtifact, publishArtifact)))
     }
 
     def "can add artifact to several configurations"() {
@@ -86,26 +114,18 @@ class DefaultLocalComponentMetaDataTest extends Specification {
         def file = new File("artifact.zip")
 
         given:
-        moduleDescriptor.addConfiguration(new Configuration("conf1"))
-        moduleDescriptor.addConfiguration(new Configuration("conf2"))
-        moduleDescriptor.addConfiguration(new Configuration("conf3"))
+        addConfiguration("conf1")
+        addConfiguration("conf2")
 
         when:
-        metaData.addArtifact("conf1", artifact, file)
-        metaData.addArtifact("conf2", artifact, file)
+        def publishArtifact = new DefaultPublishArtifact(artifact.name, artifact.extension, artifact.type, artifact.classifier, new Date(), file)
+        addArtifact("conf1", publishArtifact)
+        addArtifact("conf2", publishArtifact)
 
         then:
-        metaData.artifacts.size() == 1
-
-        and:
         def resolveMetaData = metaData.toResolveMetaData()
-        resolveMetaData.artifacts.size() == 1
-
-        and:
-        moduleDescriptor.getArtifacts("conf1").size() == 1
-        moduleDescriptor.getArtifacts("conf2").size() == 1
-        def ivyArtifact = (moduleDescriptor.getArtifacts("conf1") as List).first()
-        ivyArtifact.configurations == ["conf1", "conf2"]
+        resolveMetaData.getConfiguration("conf1").artifacts.size() == 1
+        resolveMetaData.getConfiguration("conf1").artifacts == resolveMetaData.getConfiguration("conf2").artifacts
     }
 
     def "can lookup an artifact given an Ivy artifact"() {
@@ -113,28 +133,28 @@ class DefaultLocalComponentMetaDataTest extends Specification {
         def file = new File("artifact.zip")
 
         given:
-        moduleDescriptor.addConfiguration(new Configuration("conf"))
+        addConfiguration("conf")
 
         and:
-        metaData.addArtifact("conf", artifact, file)
+        addArtifact("conf", artifact, file)
 
         and:
-        def ivyArtifact = metaData.toResolveMetaData().descriptor.allArtifacts.find { it.name == artifact.name }
+        def ivyArtifact = artifactName()
 
         expect:
-        def resolveArtifact = metaData.toResolveMetaData().artifact(ivyArtifact)
+        def resolveArtifact = metaData.toResolveMetaData().getConfiguration("conf").artifact(ivyArtifact)
         resolveArtifact.file == file
-        resolveArtifact == metaData.getArtifact(resolveArtifact.id)
     }
 
     def "can lookup an unknown artifact given an Ivy artifact"() {
-        def artifact = artifact()
+        def artifact = artifactName()
+        given:
+        addConfiguration("conf")
 
         expect:
-        def resolveArtifact = metaData.toResolveMetaData().artifact(artifact)
+        def resolveArtifact = metaData.toResolveMetaData().getConfiguration("conf").artifact(artifact)
         resolveArtifact != null
         resolveArtifact.file == null
-        metaData.getArtifact(resolveArtifact.id) == null
     }
 
     def "treats as distinct two artifacts with duplicate attributes and different files"() {
@@ -144,10 +164,10 @@ class DefaultLocalComponentMetaDataTest extends Specification {
         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)
+        addConfiguration("conf1")
+        addConfiguration("conf2")
+        addArtifact("conf1", artifact1, file1)
+        addArtifact("conf2", artifact2, file2)
 
         when:
         def resolveMetaData = metaData.toResolveMetaData()
@@ -165,54 +185,23 @@ class DefaultLocalComponentMetaDataTest extends Specification {
         artifactMetadata1.id != artifactMetadata2.id
 
         and:
-        resolveMetaData.artifacts == [artifactMetadata1, artifactMetadata2] as Set
-
-        and:
-        metaData.getArtifact(artifactMetadata1.id).file == file1
-        metaData.getArtifact(artifactMetadata2.id).file == file2
+        resolveMetaData.getConfiguration("conf1").artifacts == [artifactMetadata1] as Set
+        resolveMetaData.getConfiguration("conf2").artifacts == [artifactMetadata2] as Set
     }
 
     def "can add dependencies"() {
-        def dependencyDescriptor = Stub(DependencyDescriptor)
-        def dependency = Stub(DependencyMetaData) {
-            getDescriptor() >> dependencyDescriptor
-        }
+        def dependency = Mock(DependencyMetaData)
 
         when:
         metaData.addDependency(dependency)
 
         then:
-        metaData.moduleDescriptor.dependencies as List == [dependencyDescriptor]
         metaData.toResolveMetaData().dependencies == [dependency]
-        metaData.toResolveMetaData().descriptor.dependencies as List == [dependencyDescriptor]
-    }
-
-    def "can convert to publish meta-data"() {
-        def artifact = artifactName()
-        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.name == artifact.name
-        publishArtifact.artifact.type == artifact.type
-        publishArtifact.artifact.ext == artifact.extension
-        publishArtifact.file == file
-    }
 
-    def artifact() {
-        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(), null, "artifact", "type", "ext")
+        // TODO:DAZ Test conversion of dependency meta data for publishing
+//        and:
+//        def ivyDependencies = metaData.toPublishMetaData().getModuleDescriptor().dependencies
+//        ivyDependencies.length == 1
     }
 
     def artifactName() {
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/MissingLocalArtifactMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/MissingLocalArtifactMetaDataTest.groovy
new file mode 100644
index 0000000..c505e14
--- /dev/null
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/local/model/MissingLocalArtifactMetaDataTest.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.component.local.model
+
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
+import org.gradle.internal.component.model.DefaultIvyArtifactName
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class MissingLocalArtifactMetaDataTest extends Specification {
+    def file = new File(".")
+    def "has useful string representation"() {
+        def componentId = Stub(ComponentIdentifier)
+
+        expect:
+        def noClassifier = localArtifactIdentifier(componentId, "<comp>", "name", "type", "ext", [:])
+        noClassifier.displayName == "name.ext (<comp>)"
+        noClassifier.toString() == "name.ext (<comp>)"
+
+        def withClassifier = localArtifactIdentifier(componentId, "<comp>", "name", "type", "ext", ['classifier': 'classifier'])
+        withClassifier.displayName == "name-classifier.ext (<comp>)"
+        withClassifier.toString() == "name-classifier.ext (<comp>)"
+
+        def noExtension = localArtifactIdentifier(componentId, "<comp>", "name", "type", null, ['classifier': 'classifier'])
+        noExtension.displayName == "name-classifier (<comp>)"
+        noExtension.toString() == "name-classifier (<comp>)"
+    }
+
+    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 = localArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': 'classifier'])
+        def same = localArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': 'classifier'])
+        def differentName = localArtifactIdentifier(componentId, "comp", "2", "type", "ext", ['classifier': 'classifier'])
+        def differentType = localArtifactIdentifier(componentId, "comp", "name", "2", "ext", ['classifier': 'classifier'])
+        def differentExt = localArtifactIdentifier(componentId, "comp", "name", "type", "2", ['classifier': 'classifier'])
+        def differentAttributes = localArtifactIdentifier(componentId, "comp", "name", "type", "ext", ['classifier': '2'])
+        def emptyParts = localArtifactIdentifier(componentId, "comp", "name", "type", null, [:])
+        def emptyPartsSame = localArtifactIdentifier(componentId, "comp", "name", "type", null, [:])
+        def differentFile = new MissingLocalArtifactMetaData(componentId, "comp", new DefaultIvyArtifactName("name", "type", "ext", ['classifier': '2']))
+
+        expect:
+        withClassifier Matchers.strictlyEqual(same)
+        withClassifier != differentName
+        withClassifier != differentType
+        withClassifier != differentExt
+        withClassifier != differentAttributes
+        withClassifier != emptyParts
+        withClassifier != differentFile
+
+        emptyParts Matchers.strictlyEqual(emptyPartsSame)
+        emptyParts != withClassifier
+    }
+
+    def localArtifactIdentifier(def componentId, def displayName, def name, def type, def extension, Map<String, String> attributes) {
+        return new MissingLocalArtifactMetaData(componentId, displayName, new DefaultIvyArtifactName(name, type, extension, attributes))
+    }
+}
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultDependencyMetaDataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultDependencyMetaDataTest.groovy
index 7ddc8cb..efce032 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultDependencyMetaDataTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultDependencyMetaDataTest.groovy
@@ -140,8 +140,8 @@ class DefaultDependencyMetaDataTest extends Specification {
         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
+        toConfiguration.artifact({it.name == 'art1'}) >> artifact1
+        toConfiguration.artifact({it.name == 'art3'}) >> artifact2
 
         expect:
         metaData.getArtifacts(fromConfiguration, toConfiguration) == [artifact1, artifact2] as Set
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultIvyArtifactNameTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultIvyArtifactNameTest.groovy
index 2a4729b..54324eb 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultIvyArtifactNameTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/DefaultIvyArtifactNameTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.internal.component.model
 
+import org.gradle.api.artifacts.PublishArtifact
 import org.gradle.util.Matchers
 import spock.lang.Specification
 
@@ -62,4 +63,30 @@ class DefaultIvyArtifactNameTest extends Specification {
         emptyClassifier.classifier == null
         noClassifier.classifier == null
     }
+
+    def "creates for PublishArtifact"() {
+        def publishArtifact = Mock(PublishArtifact) {
+            getExtension() >> "art-ext"
+            getType() >> "art-type"
+            getClassifier() >> "art-classifier"
+        }
+
+        when:
+        1 * publishArtifact.getName() >> "art-name"
+
+        then:
+        def name = DefaultIvyArtifactName.forPublishArtifact(publishArtifact, "default-name")
+        name.name == "art-name"
+        name.extension == "art-ext"
+        name.type == "art-type"
+        name.classifier == "art-classifier"
+        name.attributes == [classifier: "art-classifier"]
+
+        when:
+        1 * publishArtifact.getName() >> null
+
+        then:
+        def nameWithDefault = DefaultIvyArtifactName.forPublishArtifact(publishArtifact, "default-name")
+        nameWithDefault.name == "default-name"
+    }
 }
diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy
index a731f49..085fe4f 100644
--- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy
+++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.internal.resolve.result
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 import org.gradle.internal.component.external.model.ModuleComponentResolveMetaData
 import org.gradle.internal.component.model.ComponentResolveMetaData
 import org.gradle.internal.resolve.ModuleVersionNotFoundException
@@ -102,10 +103,10 @@ class DefaultBuildableComponentResolveResultTest extends Specification {
         result.failure == null
     }
 
-    def "fails with not found exception when not found using module version id"() {
-        def id = Mock(ModuleVersionIdentifier) {
+    def "fails with not found exception when not found using module component id"() {
+        def id = Mock(ModuleComponentIdentifier) {
             it.group >> "org.gradle"
-            it.name >> "core"
+            it.module >> "core"
             it.version >> "2.3"
         }
 
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/components/ComponentReportIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/components/ComponentReportIntegrationTest.groovy
index b182da1..64c63e1 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/components/ComponentReportIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/components/ComponentReportIntegrationTest.groovy
@@ -18,13 +18,15 @@ package org.gradle.api.reporting.components
 
 import org.gradle.api.JavaVersion
 import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 
-class ComponentReportIntegrationTest extends AbstractComponentReportIntegrationTest {
+class ComponentReportIntegrationTest extends NativeComponentReportIntegrationTest {
     private JavaVersion currentJvm = JavaVersion.current()
     private String currentJava = "java" + currentJvm.majorVersion
     private String currentJdk = String.format("JDK %s (%s)", currentJvm.majorVersion, currentJvm);
     private String currentNative = NativePlatformsTestFixture.defaultPlatformName
-
+    
+    @RequiresInstalledToolChain
     def "informs the user when project has no components defined"() {
         when:
         succeeds "components"
@@ -35,6 +37,7 @@ No components defined for this project.
 """
     }
 
+    @RequiresInstalledToolChain
     def "shows details of multiple components"() {
         given:
         buildFile << """
@@ -67,9 +70,9 @@ JVM library 'jvmLib'
 
 Source sets
     Java source 'jvmLib:java'
-        src/jvmLib/java
+        srcDir: src/jvmLib/java
     JVM resources 'jvmLib:resources'
-        src/jvmLib/resources
+        srcDir: src/jvmLib/resources
 
 Binaries
     Jar 'jvmLibJar'
@@ -83,9 +86,9 @@ Native library 'nativeLib'
 
 Source sets
     C source 'nativeLib:c'
-        src/nativeLib/c
+        srcDir: src/nativeLib/c
     C++ source 'nativeLib:cpp'
-        src/nativeLib/cpp
+        srcDir: src/nativeLib/cpp
 
 Binaries
     Shared library 'nativeLib:sharedLibrary'
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy
index 62cd7fd..fbd7cef 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy
@@ -18,14 +18,31 @@ package org.gradle.api.reporting.model
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
 class ModelReportIntegrationTest extends AbstractIntegrationSpec {
+
     def "displays basic structure of an empty project"() {
+        given:
+        buildFile
+
         when:
         run "model"
 
         then:
-        // just check that it doesn't blow up for now
-        output.contains("tasks")
+        output.contains(toPlatformLineSeparators(
+            """model
+    tasks
+        components = task ':components'
+        dependencies = task ':dependencies'
+        dependencyInsight = task ':dependencyInsight'
+        help = task ':help'
+        init = task ':init'
+        model = task ':model'
+        projects = task ':projects'
+        properties = task ':properties'
+        tasks = task ':tasks'
+"""))
     }
 
     def "displays basic structure of a polyglot project"() {
@@ -45,13 +62,137 @@ model {
     }
 }
 """
+        buildFile
+        when:
+        run "model"
+
+        then:
+        def report = new ConsoleReportOutput(output)
+        report.hasTitle('Root project')
+        report.hasRootNode('model')
+        report.hasNodeStructure(
+            """model
+    binaries
+        jvmLibJar
+            tasks
+        nativeLibSharedLibrary
+            tasks
+        nativeLibStaticLibrary
+            tasks
+    binaryNamingSchemeBuilder
+    binarySpecFactory
+    buildTypes
+    componentSpecFactory
+    components
+        jvmLib
+            binaries
+                jvmLibJar
+                    tasks
+            sources
+                java
+                resources
+        nativeLib
+            binaries
+                nativeLibSharedLibrary
+                    tasks
+                nativeLibStaticLibrary
+                    tasks
+            sources
+                c
+                cpp
+    flavors
+    javaToolChain
+    jvm
+    languageTransforms
+    languages
+    platformResolver
+    platforms
+    repositories
+    sources
+    tasks
+        assemble
+        build
+        check
+        clean
+        components
+        createJvmLibJar
+        createNativeLibStaticLibrary
+        dependencies
+        dependencyInsight
+        help
+        init
+        jvmLibJar
+        linkNativeLibSharedLibrary
+        model
+        nativeLibSharedLibrary
+        nativeLibStaticLibrary
+        projects
+        properties
+        tasks
+        wrapper
+    toolChains"""
+        )
+    }
+
+    def "displays basic values of a simple model graph with values"() {
+        given:
+        buildFile << """
+
+ at Managed
+public interface PasswordCredentials {
+    String getUsername()
+    String getPassword()
+    void setUsername(String s)
+    void setPassword(String s)
+}
+
+
+ at Managed
+public interface Numbers {
+    Integer getValue()
+    void setValue(Integer i)
+}
+
+model {
+    primaryCredentials(PasswordCredentials){
+        username = 'uname'
+        password = 'hunter2'
+    }
 
+    nullCredentials(PasswordCredentials) { }
+    numbers(Numbers){
+        value = 5
+    }
+}
+
+"""
+        buildFile
         when:
         run "model"
 
         then:
-        // just check that it doesn't blow up for now
-        output.contains("components")
-        output.contains("tasks")
+
+        output.contains(toPlatformLineSeparators(
+            """model
+    nullCredentials
+        password
+        username
+    numbers
+        value = 5
+    primaryCredentials
+        password = hunter2
+        username = uname
+    tasks
+        components = task ':components'
+        dependencies = task ':dependencies'
+        dependencyInsight = task ':dependencyInsight'
+        help = task ':help'
+        init = task ':init'
+        model = task ':model'
+        projects = task ':projects'
+        properties = task ':properties'
+        tasks = task ':tasks'
+"""))
+
     }
 }
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..d7494ec
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportTaskIntegrationTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.model
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+
+class ModelReportTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    def "should display the model report task options"() {
+        when:
+        run "help", "--task", "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("Displays the configuration model of root project '${getTestDirectory().name}'. [incubating]"))
+    }
+
+}
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 0c12187..04c0461 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
@@ -297,7 +297,7 @@ org:leaf:latest.integration -> 1.0
 """))
     }
 
-    def "shows substituted versions"() {
+    def "shows versions substitute by resolve rule"() {
         given:
         mavenRepo.module("org", "leaf", 1.0).publish()
         mavenRepo.module("org", "leaf", 2.0).publish()
@@ -342,7 +342,7 @@ org:leaf:2.0 -> 1.0
         given:
         mavenRepo.module("org", "new-leaf", 77).publish()
 
-        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "foo", 2.0).dependsOn('org', 'leaf', '1.0').publish()
         mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
 
         file("build.gradle") << """
@@ -351,7 +351,10 @@ org:leaf:2.0 -> 1.0
             }
             configurations {
                 conf {
-                    resolutionStrategy.eachDependency { if (it.requested.name == 'leaf') { it.useTarget('org:new-leaf:77') } }
+                    resolutionStrategy.dependencySubstitution {
+                        substitute module('org:leaf') with module('org:new-leaf:77')
+                        substitute module('org:foo') with module('org:foo:2.0')
+                    }
                 }
             }
             dependencies {
@@ -371,7 +374,7 @@ org:leaf:2.0 -> 1.0
 org:new-leaf:77 (selected by rule)
 
 org:leaf:1.0 -> org:new-leaf:77
-\\--- org:foo:1.0
+\\--- org:foo:2.0
      \\--- conf
 
 org:leaf:2.0 -> org:new-leaf:77
@@ -729,7 +732,9 @@ org:middle:1.0 -> 2.0 FAILED
             }
             configurations {
                 conf {
-                    resolutionStrategy.eachDependency { if (it.requested.name == 'middle') { it.useVersion('2.0+') } }
+                    resolutionStrategy.dependencySubstitution {
+                        substitute module("org:middle") with module("org:middle:2.0+")
+                    }
                 }
             }
             dependencies {
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 8ee0e90..657a601 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
@@ -681,11 +681,11 @@ compile - Compile classpath for source set 'main'.
 """))
     }
 
-    def "reports external dependency replaced to project dependency"()
+    def "reports external dependency replaced with project dependency"()
     {
         mavenRepo.module("org.utils", "api",  '1.3').publish()
 
-        file("settings.gradle") << "include 'client', 'api', 'impl'"
+        file("settings.gradle") << "include 'client', 'api2', 'impl'"
 
         buildFile << """
             allprojects {
@@ -698,10 +698,10 @@ compile - Compile classpath for source set 'main'.
                     compile
                 }
 
-                group "org.utils"
+                group "org.somethingelse"
             }
 
-            project(":api") {
+            project(":api2") {
                 version = '1.5'
             }
 
@@ -710,10 +710,8 @@ compile - Compile classpath for source set 'main'.
                     compile group: 'org.utils', name: 'api', version: '1.3', configuration: 'compile'
                 }
 
-                configurations.compile.resolutionStrategy.dependencySubstitution.all {
-                    if (it.requested.version == '1.3') {
-                        it.useTarget project(":api")
-                    }
+                configurations.compile.resolutionStrategy.dependencySubstitution {
+                    substitute module('org.utils:api:1.3') with project(':api2')
                 }
             }
 """
@@ -724,15 +722,14 @@ compile - Compile classpath for source set 'main'.
         then:
         output.contains(toPlatformLineSeparators("""
 compile
-\\--- org.utils:api:1.3 -> project :api
+\\--- org.utils:api:1.3 -> project :api2
 """))
     }
 
-    def "reports external dependency replaced to project dependency with other group/name"()
-    {
-        mavenRepo.module("org.utils", "api",  '1.3').publish()
+    def "reports external dependency with version updated by resolve rule"() {
+        mavenRepo.module("org.utils", "api", '0.1').publish()
 
-        file("settings.gradle") << "include 'client', 'api2', 'impl'"
+        file("settings.gradle") << "include 'client', 'impl'"
 
         buildFile << """
             allprojects {
@@ -745,21 +742,17 @@ compile
                     compile
                 }
 
-                group "org.somethingelse"
-            }
-
-            project(":api2") {
-                version = '1.5'
+                group "org.utils"
             }
 
             project(":impl") {
                 dependencies {
-                    compile group: 'org.utils', name: 'api', version: '1.3', configuration: 'compile'
+                    compile group: 'org.utils', name: 'api', version: '1.3'
                 }
 
-                configurations.compile.resolutionStrategy.dependencySubstitution.all {
+                configurations.compile.resolutionStrategy.eachDependency {
                     if (it.requested.version == '1.3') {
-                        it.useTarget project(":api2")
+                        it.useVersion '0.1'
                     }
                 }
             }
@@ -771,12 +764,13 @@ compile
         then:
         output.contains(toPlatformLineSeparators("""
 compile
-\\--- org.utils:api:1.3 -> project :api2
+\\--- org.utils:api:1.3 -> 0.1
 """))
     }
 
-    def "reports external dependency name and version change"() {
-        mavenRepo.module("org.utils", "api2", '0.1').publish()
+    def "reports external dependency substituted with another"() {
+        mavenRepo.module("org.utils", "api", '0.1').publish()
+        mavenRepo.module("org.other", "another", '0.1').publish()
 
         file("settings.gradle") << "include 'client', 'impl'"
 
@@ -797,12 +791,12 @@ compile
             project(":impl") {
                 dependencies {
                     compile group: 'org.utils', name: 'api', version: '1.3'
+                    compile group: 'org.original', name: 'original', version: '1.0'
                 }
 
-                configurations.compile.resolutionStrategy.eachDependency {
-                    if (it.requested.version == '1.3') {
-                        it.useTarget group: 'org.utils', name: 'api2', version: '0.1'
-                    }
+                configurations.compile.resolutionStrategy.dependencySubstitution {
+                    substitute module('org.original:original') with module('org.other:another:0.1')
+                    substitute module('org.utils:api') with module('org.utils:api:0.1')
                 }
             }
 """
@@ -813,7 +807,8 @@ compile
         then:
         output.contains(toPlatformLineSeparators("""
 compile
-\\--- org.utils:api:1.3 -> org.utils:api2:0.1
++--- org.utils:api:1.3 -> 0.1
+\\--- org.original:original:1.0 -> org.other:another:0.1
 """))
     }
 }
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/ComponentReport.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/ComponentReport.java
index 5020506..16bd200 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/ComponentReport.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/ComponentReport.java
@@ -29,10 +29,10 @@ import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.model.internal.core.ModelPath;
 import org.gradle.model.internal.registry.ModelRegistry;
 import org.gradle.model.internal.type.ModelType;
-import org.gradle.platform.base.test.TestSuiteContainer;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.platform.base.ComponentSpec;
 import org.gradle.platform.base.ComponentSpecContainer;
+import org.gradle.platform.base.test.TestSuiteContainer;
 
 import javax.inject.Inject;
 import java.util.ArrayList;
@@ -76,12 +76,12 @@ public class ComponentReport extends DefaultTask {
         Collection<ComponentSpec> components = new ArrayList<ComponentSpec>();
         ComponentSpecContainer componentSpecs = getModelRegistry().find(ModelPath.path("components"), ModelType.of(ComponentSpecContainer.class));
         if (componentSpecs != null) {
-            components.addAll(componentSpecs);
+            components.addAll(componentSpecs.values());
         }
 
         TestSuiteContainer testSuites = getModelRegistry().find(ModelPath.path("testSuites"), ModelType.of(TestSuiteContainer.class));
         if (testSuites != null) {
-            components.addAll(testSuites);
+            components.addAll(testSuites.values());
         }
 
         renderer.renderComponents(components);
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentRenderer.java
index a57509f..a1b3eb9 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentRenderer.java
@@ -24,8 +24,6 @@ import org.gradle.platform.base.ComponentSpec;
 import org.gradle.reporting.ReportRenderer;
 import org.gradle.util.CollectionUtils;
 
-import java.util.Comparator;
-
 public class ComponentRenderer extends ReportRenderer<ComponentSpec, TextReportBuilder> {
     private final ReportRenderer<LanguageSourceSet, TextReportBuilder> sourceSetRenderer;
     private final ReportRenderer<BinarySpec, TextReportBuilder> binaryRenderer;
@@ -39,12 +37,8 @@ public class ComponentRenderer extends ReportRenderer<ComponentSpec, TextReportB
     public void render(ComponentSpec component, TextReportBuilder builder) {
         builder.subheading(StringUtils.capitalize(component.getDisplayName()));
         builder.getOutput().println();
-        builder.collection("Source sets", component.getSource(), sourceSetRenderer, "source sets");
+        builder.collection("Source sets", CollectionUtils.sort(component.getSource().values(), SourceSetRenderer.SORT_ORDER), sourceSetRenderer, "source sets");
         builder.getOutput().println();
-        builder.collection("Binaries", CollectionUtils.sort(component.getBinaries(), new Comparator<BinarySpec>() {
-            public int compare(BinarySpec binary1, BinarySpec binary2) {
-                return binary1.getName().compareTo(binary2.getName());
-            }
-        }), binaryRenderer, "binaries");
+        builder.collection("Binaries", CollectionUtils.sort(component.getBinaries().values(), TypeAwareBinaryRenderer.SORT_ORDER), binaryRenderer, "binaries");
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentReportRenderer.java
index 6d78494..547b95e 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentReportRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/ComponentReportRenderer.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.reporting.components.internal;
 
+import com.google.common.collect.Sets;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
@@ -24,10 +25,7 @@ import org.gradle.platform.base.BinarySpec;
 import org.gradle.platform.base.ComponentSpec;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.*;
 
 import static org.gradle.logging.StyledTextOutput.Style.Info;
 
@@ -65,13 +63,13 @@ public class ComponentReportRenderer extends TextReportRenderer {
                 seen = true;
             }
             componentRenderer.render(component, getBuilder());
-            componentSourceSets.addAll(component.getSource());
-            componentBinaries.addAll(component.getBinaries());
+            componentSourceSets.addAll(component.getSource().values());
+            componentBinaries.addAll(component.getBinaries().values());
         }
     }
 
     public void renderSourceSets(Collection<LanguageSourceSet> sourceSets) {
-        Set<LanguageSourceSet> additionalSourceSets = new LinkedHashSet<LanguageSourceSet>();
+        Set<LanguageSourceSet> additionalSourceSets = Sets.newTreeSet(SourceSetRenderer.SORT_ORDER);
         for (LanguageSourceSet sourceSet : sourceSets) {
             if (!componentSourceSets.contains(sourceSet)) {
                 additionalSourceSets.add(sourceSet);
@@ -87,7 +85,7 @@ public class ComponentReportRenderer extends TextReportRenderer {
     }
 
     public void renderBinaries(Collection<BinarySpec> binaries) {
-        Set<BinarySpec> additionalBinaries = new LinkedHashSet<BinarySpec>();
+        Set<BinarySpec> additionalBinaries = Sets.newTreeSet(TypeAwareBinaryRenderer.SORT_ORDER);
         for (BinarySpec binary : binaries) {
             if (!componentBinaries.contains(binary)) {
                 additionalBinaries.add(binary);
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/SourceSetRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/SourceSetRenderer.java
index 15f87ee..ee60093 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/SourceSetRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/SourceSetRenderer.java
@@ -16,28 +16,48 @@
 
 package org.gradle.api.reporting.components.internal;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder;
 import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.DependentSourceSetInternal;
 import org.gradle.logging.StyledTextOutput;
+import org.gradle.platform.base.DependencySpec;
+import org.gradle.platform.base.DependencySpecContainer;
 import org.gradle.reporting.ReportRenderer;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Set;
 
 class SourceSetRenderer extends ReportRenderer<LanguageSourceSet, TextReportBuilder> {
+    static final Comparator<LanguageSourceSet> SORT_ORDER = new Comparator<LanguageSourceSet>() {
+        @Override
+        public int compare(LanguageSourceSet o1, LanguageSourceSet o2) {
+            return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
+        }
+    };
+
     @Override
     public void render(LanguageSourceSet sourceSet, TextReportBuilder builder) {
         StyledTextOutput textOutput = builder.getOutput();
         textOutput.println(StringUtils.capitalize(sourceSet.getDisplayName()));
+        renderSourceSetDirectories(sourceSet, builder);
+        renderSourceSetDependencies(sourceSet, builder);
+    }
+
+    private void renderSourceSetDirectories(LanguageSourceSet sourceSet, TextReportBuilder builder) {
         Set<File> srcDirs = sourceSet.getSource().getSrcDirs();
         if (srcDirs.isEmpty()) {
-            textOutput.println("    No source directories");
+            builder.item("No source directories");
         } else {
             for (File file : srcDirs) {
-                builder.item(file);
+                builder.item("srcDir", file);
             }
             SourceDirectorySet source = sourceSet.getSource();
             Set<String> includes = source.getIncludes();
@@ -50,4 +70,26 @@ class SourceSetRenderer extends ReportRenderer<LanguageSourceSet, TextReportBuil
             }
         }
     }
+
+    private void renderSourceSetDependencies(LanguageSourceSet sourceSet, TextReportBuilder builder) {
+        if (sourceSet instanceof DependentSourceSetInternal) {
+            DependencySpecContainer dependencies = ((DependentSourceSetInternal) sourceSet).getDependencies();
+            if (!dependencies.isEmpty()) {
+                builder.item("dependencies");
+                builder.collection(dependencies, new ReportRenderer<DependencySpec, TextReportBuilder>() {
+                    @Override
+                    public void render(DependencySpec model, TextReportBuilder output) throws IOException {
+                        List<String> parts = Lists.newArrayList();
+                        if (model.getProjectPath() != null) {
+                            parts.add("project '" + model.getProjectPath() + "'");
+                        }
+                        if (model.getLibraryName() != null) {
+                            parts.add("library '" + model.getLibraryName() + "'");
+                        }
+                        output.item(Joiner.on(' ').join(parts));
+                    }
+                });
+            }
+        }
+    }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/TypeAwareBinaryRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/TypeAwareBinaryRenderer.java
index 39706e2..b4c4524 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/TypeAwareBinaryRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/components/internal/TypeAwareBinaryRenderer.java
@@ -21,10 +21,16 @@ import org.gradle.platform.base.BinarySpec;
 import org.gradle.reporting.ReportRenderer;
 
 import java.io.IOException;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 
 public class TypeAwareBinaryRenderer extends ReportRenderer<BinarySpec, TextReportBuilder> {
+    static final Comparator<BinarySpec> SORT_ORDER = new Comparator<BinarySpec>() {
+        public int compare(BinarySpec binary1, BinarySpec binary2) {
+            return binary1.getName().compareTo(binary2.getName());
+        }
+    };
     private final Map<Class<?>, ReportRenderer<BinarySpec, TextReportBuilder>> renderers = new HashMap<Class<?>, ReportRenderer<BinarySpec, TextReportBuilder>>();
 
     public void register(AbstractBinaryRenderer<?> renderer) {
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/ModelReport.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/ModelReport.java
index 0e5e501..bf9d18b 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/ModelReport.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/ModelReport.java
@@ -19,11 +19,11 @@ package org.gradle.api.reporting.model;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Incubating;
 import org.gradle.api.Project;
-import org.gradle.api.reporting.model.internal.ModelReportRenderer;
+import org.gradle.api.reporting.model.internal.ModelNodeRenderer;
+import org.gradle.api.reporting.model.internal.TextModelReportRenderer;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.model.internal.core.ModelNode;
 import org.gradle.model.internal.core.ModelPath;
 import org.gradle.model.internal.registry.ModelRegistry;
 
@@ -34,6 +34,7 @@ import javax.inject.Inject;
  */
 @Incubating
 public class ModelReport extends DefaultTask {
+
     @Inject
     protected StyledTextOutputFactory getTextOutputFactory() {
         throw new UnsupportedOperationException();
@@ -47,18 +48,13 @@ public class ModelReport extends DefaultTask {
     @TaskAction
     public void report() {
         Project project = getProject();
-
         StyledTextOutput textOutput = getTextOutputFactory().create(ModelReport.class);
-        ModelReportRenderer renderer = new ModelReportRenderer();
-        renderer.setOutput(textOutput);
-
-        renderer.startProject(project);
-
-        // Configure the world
-        ModelNode rootNode = getModelRegistry().realizeNode(ModelPath.ROOT);
-        renderer.render(rootNode);
-
-        renderer.completeProject(project);
-        renderer.complete();
+        ModelNodeRenderer renderer = new ModelNodeRenderer();
+        TextModelReportRenderer textModelReportRenderer = new TextModelReportRenderer(renderer);
+        textModelReportRenderer.setOutput(textOutput);
+        textModelReportRenderer.startProject(project);
+        textModelReportRenderer.render(getModelRegistry().realizeNode(ModelPath.ROOT));
+        textModelReportRenderer.completeProject(project);
+        textModelReportRenderer.complete();
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelNodeRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelNodeRenderer.java
new file mode 100644
index 0000000..a2ab3fb
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelNodeRenderer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.model.internal;
+
+import com.google.common.base.Optional;
+import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.model.internal.core.ModelNode;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.reporting.ReportRenderer;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.gradle.logging.StyledTextOutput.Style.Description;
+import static org.gradle.logging.StyledTextOutput.Style.Identifier;
+
+public class ModelNodeRenderer extends ReportRenderer<ModelNode, TextReportBuilder> {
+
+    @Override
+    public void render(ModelNode model, TextReportBuilder output) {
+        if (model.isHidden()) {
+            return;
+        }
+
+        StyledTextOutput styledTextoutput = output.getOutput();
+        if (model.getPath().equals(ModelPath.ROOT)) {
+            styledTextoutput.println("model");
+        } else {
+            styledTextoutput.withStyle(Identifier).format("%s", model.getPath().getName());
+            if (model.getLinkCount() == 0) {
+                Optional<String> value = model.getValueDescription();
+                if (value.isPresent()) {
+                    styledTextoutput.withStyle(Description).format(" = %s", value.get());
+                }
+            }
+            styledTextoutput.println();
+        }
+
+        Map<String, ModelNode> links = new TreeMap<String, ModelNode>();
+        for (ModelNode node : model.getLinks(ModelType.untyped())) {
+            links.put(node.getPath().getName(), node);
+        }
+        output.collection(links.values(), this);
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelReportRenderer.java
deleted file mode 100644
index b52ad51..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/ModelReportRenderer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.reporting.model.internal;
-
-import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
-import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder;
-import org.gradle.model.internal.core.ModelNode;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.type.ModelType;
-import org.gradle.reporting.ReportRenderer;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-public class ModelReportRenderer extends TextReportRenderer {
-    private final NodeRenderer nodeRenderer = new NodeRenderer();
-
-    public void render(ModelNode node) {
-        nodeRenderer.render(node, getBuilder());
-    }
-
-    private static class NodeRenderer extends ReportRenderer<ModelNode, TextReportBuilder> {
-        @Override
-        public void render(ModelNode model, TextReportBuilder output) {
-            if (model.isHidden()) {
-                return;
-            }
-
-            if (model.getPath().equals(ModelPath.ROOT)) {
-                output.getOutput().println("model");
-            } else {
-                output.getOutput().println(model.getPath().getName());
-            }
-
-            Map<String, ModelNode> links = new TreeMap<String, ModelNode>();
-            for (ModelNode node : model.getLinks(ModelType.untyped())) {
-                links.put(node.getPath().getName(), node);
-            }
-            output.collection(links.values(), this);
-        }
-    }
-}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/TextModelReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/TextModelReportRenderer.java
new file mode 100644
index 0000000..4895dac
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/reporting/model/internal/TextModelReportRenderer.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.reporting.model.internal;
+
+import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
+import org.gradle.model.internal.core.ModelNode;
+
+public class TextModelReportRenderer extends TextReportRenderer {
+    private final ModelNodeRenderer modelNodeRenderer;
+
+    public TextModelReportRenderer(ModelNodeRenderer modelNodeRenderer) {
+        this.modelNodeRenderer = modelNodeRenderer;
+    }
+
+    public void render(ModelNode node) {
+        modelNodeRenderer.render(node, getBuilder());
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/text/DefaultTextReportBuilder.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/text/DefaultTextReportBuilder.java
index 9205747..4a30081 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/text/DefaultTextReportBuilder.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/text/DefaultTextReportBuilder.java
@@ -41,7 +41,7 @@ public class DefaultTextReportBuilder implements TextReportBuilder {
 
     public void item(String title, String value) {
         textOutput.append("    ").append(title).append(": ");
-        StyledTextOutput itemOutput = new LinePrefixingStyledTextOutput(textOutput, "    ");
+        StyledTextOutput itemOutput = new LinePrefixingStyledTextOutput(textOutput, "    ", false);
         itemOutput.append(value).println();
     }
 
@@ -50,7 +50,6 @@ public class DefaultTextReportBuilder implements TextReportBuilder {
     }
 
     public void item(String value) {
-        textOutput.append("    ");
         StyledTextOutput itemOutput = new LinePrefixingStyledTextOutput(textOutput, "    ");
         itemOutput.append(value).println();
     }
@@ -88,15 +87,9 @@ public class DefaultTextReportBuilder implements TextReportBuilder {
     @Override
     public <T> void collection(Iterable<? extends T> items, ReportRenderer<T, TextReportBuilder> renderer) {
         StyledTextOutput original = textOutput;
-        boolean hasItem = false;
         try {
             textOutput = new LinePrefixingStyledTextOutput(original, "    ");
             for (T t : items) {
-                // TODO - change LinePrefixingStyledTextOutput to prefix every line
-                if (!hasItem) {
-                    textOutput.append("    ");
-                    hasItem = true;
-                }
                 try {
                     renderer.render(t, this);
                 } catch (IOException e) {
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java
index 5ac4ac2..bb99d85 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/TaskDetailPrinter.java
@@ -215,6 +215,6 @@ public class TaskDetailPrinter {
     }
 
     private LinePrefixingStyledTextOutput createIndentedOutput(StyledTextOutput output, String prefix) {
-        return new LinePrefixingStyledTextOutput(output, prefix);
+        return new LinePrefixingStyledTextOutput(output, prefix, false);
     }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentRendererTest.groovy
index 1fbd553..40c4fd9 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentRendererTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentRendererTest.groovy
@@ -19,12 +19,11 @@
 package org.gradle.api.reporting.components.internal
 
 import org.gradle.api.Project
-import org.gradle.api.internal.DefaultDomainObjectSet
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.tasks.diagnostics.internal.text.DefaultTextReportBuilder
 import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder
-import org.gradle.language.base.LanguageSourceSet
 import org.gradle.logging.TestStyledTextOutput
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.BinarySpec
 import org.gradle.platform.base.ComponentSpec
 import spock.lang.Specification
@@ -43,7 +42,6 @@ class ComponentRendererTest extends Specification {
     def "renders component"() {
         def component = Stub(ComponentSpec)
         component.displayName >> "<component>"
-        component.source >> new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet)
 
         when:
         renderer.render(component, builder)
@@ -56,7 +54,6 @@ class ComponentRendererTest extends Specification {
 
     def "renders component with no source sets"() {
         def component = Stub(ComponentSpec)
-        component.source >> new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet)
 
         when:
         renderer.render(component, builder)
@@ -67,7 +64,9 @@ class ComponentRendererTest extends Specification {
 
     def "renders component with no binaries"() {
         def component = Stub(ComponentSpec)
-        component.binaries >> new DefaultDomainObjectSet<BinarySpec>(BinarySpec)
+        component.binaries >> Mock(ModelMap) {
+            values() >> []
+        }
 
         when:
         renderer.render(component, builder)
@@ -78,12 +77,9 @@ class ComponentRendererTest extends Specification {
 
     def "renders component binaries ordered by name"() {
         def component = Stub(ComponentSpec)
-        def binaries = new DefaultDomainObjectSet<BinarySpec>(BinarySpec)
-        binaries.add(binary("cBinary"))
-        binaries.add(binary("aBinary"))
-        binaries.add(binary("bBinary"))
-        binaries.add(binary("dBinary"))
-        component.binaries >> binaries
+        component.binaries >> Mock(ModelMap) {
+            values() >> [binary("cBinary"), binary("aBinary"), binary("bBinary"), binary("dBinary")]
+        }
         binaryRenderer.render(_, _) >> { BinarySpec binary, TextReportBuilder output -> output.output.println("binary: $binary.name") }
 
         when:
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentReportRendererTest.groovy
index 6f73657..7eca1f0 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentReportRendererTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/ComponentReportRendererTest.groovy
@@ -15,12 +15,13 @@
  */
 
 package org.gradle.api.reporting.components.internal
+
 import org.gradle.api.Project
-import org.gradle.api.internal.DefaultDomainObjectSet
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder
 import org.gradle.language.base.LanguageSourceSet
 import org.gradle.logging.TestStyledTextOutput
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.BinarySpec
 import org.gradle.platform.base.ComponentSpec
 import spock.lang.Specification
@@ -93,7 +94,9 @@ class ComponentReportRendererTest extends Specification {
             getDisplayName() >> "<source set>"
         }
         def component = Stub(ComponentSpec) {
-            getSource() >> set(LanguageSourceSet, sourceSet1)
+            getSource() >> Stub(ModelMap) {
+                values() >> [sourceSet1]
+            }
         }
 
         when:
@@ -116,7 +119,9 @@ class ComponentReportRendererTest extends Specification {
         def binary1 = Stub(BinarySpec)
         def binary2 = Stub(BinarySpec)
         def component = Stub(ComponentSpec) {
-            getBinaries() >> set(BinarySpec, binary1)
+            getBinaries() >> Stub(ModelMap) {
+                values() >> [binary1]
+            }
         }
         binaryRenderer.render(binary2, _) >> { BinarySpec binary, TextReportBuilder builder -> builder.output.println("<binary>")}
 
@@ -133,10 +138,4 @@ class ComponentReportRendererTest extends Specification {
 <binary>
 """)
     }
-
-    def set(Class type, Object... values) {
-        def collection = new DefaultDomainObjectSet(type)
-        collection.addAll(values)
-        return collection
-    }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/SourceSetRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/SourceSetRendererTest.groovy
index c844a21..f8a5295 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/SourceSetRendererTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/reporting/components/internal/SourceSetRendererTest.groovy
@@ -17,63 +17,96 @@
 package org.gradle.api.reporting.components.internal
 
 import org.gradle.api.file.SourceDirectorySet
-import org.gradle.api.tasks.diagnostics.internal.text.TextReportBuilder
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.tasks.diagnostics.internal.text.DefaultTextReportBuilder
 import org.gradle.language.base.LanguageSourceSet
-import org.gradle.logging.StyledTextOutput
+import org.gradle.language.base.internal.DependentSourceSetInternal
+import org.gradle.logging.TestStyledTextOutput
+import org.gradle.platform.base.DependencySpecContainer
+import org.gradle.platform.base.internal.DefaultDependencySpecContainer
 import spock.lang.Specification
 
 class SourceSetRendererTest extends Specification {
 
     SourceSetRenderer renderer = new SourceSetRenderer()
-    LanguageSourceSet languageSourceSet = Mock()
-    TextReportBuilder reportBuilder = Mock()
-    StyledTextOutput styledTextOutput = Mock()
-    SourceDirectorySet sourceDirectorySet = Mock()
+    LanguageSourceSet languageSourceSet = Mock(LanguageSourceSet)
+    SourceDirectorySet sourceDirectorySet = Mock(SourceDirectorySet)
 
-    File srcFolder1
-    File srcFolder2
+    def resolver = Stub(FileResolver) {
+        resolveAsRelativePath(_) >> { return it[0].toString() }
+    }
+    def output = new TestStyledTextOutput()
+    def builder = new DefaultTextReportBuilder(output, resolver)
+
+    File srcFolder1 = new File("src/folder1")
+    File srcFolder2 = new File("src/folder2")
 
     def setup() {
         _ * languageSourceSet.displayName >> "acme:sample"
         _ * languageSourceSet.source >> sourceDirectorySet
-        _ * reportBuilder.getOutput() >> styledTextOutput
-        1 * styledTextOutput.println("Acme:sample")
+        _ * sourceDirectorySet.srcDirs >> [srcFolder1, srcFolder2]
     }
 
     def "shows sourceSet folders"() {
         given:
-        withTwoSourceFolders()
-        1 * sourceDirectorySet.getIncludes() >> []
-        1 * sourceDirectorySet.getExcludes() >> []
+        _ * sourceDirectorySet.getIncludes() >> []
+        _ * sourceDirectorySet.getExcludes() >> []
+
         when:
-        renderer.render(languageSourceSet, reportBuilder)
+        renderer.render(languageSourceSet, builder)
+
         then:
-        1 * reportBuilder.item(srcFolder1)
-        1 * reportBuilder.item(srcFolder2)
+        output.value.startsWith("Acme:sample")
+        output.value.contains("srcDir: " + srcFolder1)
+        output.value.contains("srcDir: " + srcFolder2)
 
     }
 
     def "shows includes / excludes"() {
         given:
-        withTwoSourceFolders()
         1 * sourceDirectorySet.getIncludes() >> includes
         1 * sourceDirectorySet.getExcludes() >> excludes
+
         when:
-        renderer.render(languageSourceSet, reportBuilder)
+        renderer.render(languageSourceSet, builder)
+
         then:
-        expectedOutputs.each { key, value ->
-            1 * reportBuilder.item(key, value)
+        expectedOutputs.each {
+            output.value.contains it
         }
         where:
         includes                    | excludes                   | expectedOutputs
-        ["**/*.java"]               | ["**/gen/**"]              | [includes: "**/*.java", excludes: "**/gen/**"]
-        ["**/*.java", "**/*.scala"] | ["**/gen/**"]              | [includes: "**/*.java, **/*.scala", excludes: "**/gen/**"]
-        ["**/*.scala"]              | ["**/gen/**", "**/*.java"] | [includes: "**/*.scala", excludes: "**/gen/**, **/*.java"]
+        ["**/*.java"]               | ["**/gen/**"]              | ["includes: **/*.java", "excludes: **/gen/**"]
+        ["**/*.java", "**/*.scala"] | ["**/gen/**"]              | ["includes: **/*.java, **/*.scala", "excludes: **/gen/**"]
+        ["**/*.scala"]              | ["**/gen/**", "**/*.java"] | ["includes: **/*.scala", "excludes: **/gen/**, **/*.java"]
     }
 
-    def withTwoSourceFolders() {
-        srcFolder1 = new File("src/folder1")
-        srcFolder2 = new File("src/folder2")
-        1 * sourceDirectorySet.srcDirs >> [srcFolder1, srcFolder2]
+    def "shows dependencies"() {
+        DependencySpecContainer dsc = new DefaultDependencySpecContainer()
+        dsc.project("a-project")
+        dsc.library("a-library")
+        dsc.project("some-project").library("some-library")
+
+        given:
+        def dependentSourceSet = Mock(DependentLanguageSourceSet)
+        dependentSourceSet.dependencies >> dsc
+
+        _ * dependentSourceSet.displayName >> "acme:sample"
+        _ * dependentSourceSet.source >> sourceDirectorySet
+        _ * sourceDirectorySet.getIncludes() >> []
+        _ * sourceDirectorySet.getExcludes() >> []
+
+        when:
+        renderer.render(dependentSourceSet, builder)
+
+        then:
+        output.value.contains("""
+    dependencies
+        project 'a-project'
+        library 'a-library'
+        project 'some-project' library 'some-library'
+""")
     }
+
+    interface DependentLanguageSourceSet extends LanguageSourceSet, DependentSourceSetInternal {}
 }
diff --git a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/AbstractComponentReportIntegrationTest.groovy b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/AbstractComponentReportIntegrationTest.groovy
index 5cef101..cb35f04 100644
--- a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/AbstractComponentReportIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/AbstractComponentReportIntegrationTest.groovy
@@ -18,10 +18,13 @@
 
 package org.gradle.api.reporting.components
 
+import org.gradle.api.Transformer
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.nativeplatform.fixtures.AvailableToolChains
 
 abstract class AbstractComponentReportIntegrationTest extends AbstractIntegrationSpec {
+    Transformer<String, String> formatter = new ComponentReportOutputFormatter()
+
     def setup() {
         settingsFile << "rootProject.name = 'test'"
     }
@@ -42,7 +45,7 @@ Root project
 Note: currently not all plugins register their components, so some components may not be visible here.
 
 """
-        return new ComponentReportOutputFormatter(toolChain).transform(raw)
+        return formatter.transform(raw)
     }
 
     AvailableToolChains.InstalledToolChain getToolChain() {
diff --git a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/ComponentReportOutputFormatter.groovy b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/ComponentReportOutputFormatter.groovy
index f7f97d1..8eb90f0 100644
--- a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/ComponentReportOutputFormatter.groovy
+++ b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/ComponentReportOutputFormatter.groovy
@@ -15,32 +15,16 @@
  */
 
 package org.gradle.api.reporting.components
+
 import org.gradle.api.Transformer
 import org.gradle.internal.SystemProperties
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.nativeplatform.fixtures.AvailableToolChains
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
 
 class ComponentReportOutputFormatter implements Transformer<String, String> {
-    final AvailableToolChains.InstalledToolChain toolChain
-
-    ComponentReportOutputFormatter() {
-        this.toolChain = AvailableToolChains.getDefaultToolChain()
-    }
-
-    ComponentReportOutputFormatter(AvailableToolChains.InstalledToolChain toolChain) {
-        this.toolChain = toolChain
-    }
 
     @Override
     String transform(String original) {
          return original
-                .replace("Tool chain 'clang' (Clang)", toolChain.instanceDisplayName)
-                .replace("platform: current", "platform: " + NativePlatforms.defaultPlatformName)
                 .replace("\n", SystemProperties.instance.lineSeparator)
-                .replaceAll('(?m)(build/binaries/.+/)lib(\\w+).dylib$') { it[1] + OperatingSystem.current().getSharedLibraryName(it[2]) }
-                .replaceAll('(?m)(build/binaries/.+/)lib(\\w+).a$') { it[1] + OperatingSystem.current().getStaticLibraryName(it[2]) }
-                .replaceAll('(?m)(build/binaries/.+/)(\\w+)$') { it[1] + OperatingSystem.current().getExecutableName(it[2]) }
                 .replaceAll("(\\w+/)+\\w+") { it[0].replace('/', File.separator) }
     }
 }
diff --git a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportIntegrationTest.groovy b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportIntegrationTest.groovy
new file mode 100644
index 0000000..72a960b
--- /dev/null
+++ b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportIntegrationTest.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.api.reporting.components
+
+class NativeComponentReportIntegrationTest extends AbstractComponentReportIntegrationTest {
+    def setup() {
+        formatter = new NativeComponentReportOutputFormatter(toolChain)
+    }
+}
diff --git a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportOutputFormatter.groovy b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportOutputFormatter.groovy
new file mode 100644
index 0000000..832ac3b
--- /dev/null
+++ b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/components/NativeComponentReportOutputFormatter.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.components
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativeplatform.fixtures.AvailableToolChains
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
+
+class NativeComponentReportOutputFormatter extends ComponentReportOutputFormatter {
+    final AvailableToolChains.InstalledToolChain toolChain
+
+    NativeComponentReportOutputFormatter() {
+        this.toolChain = AvailableToolChains.getDefaultToolChain()
+    }
+
+    NativeComponentReportOutputFormatter(AvailableToolChains.InstalledToolChain toolChain) {
+        this.toolChain = toolChain
+    }
+
+    @Override
+    String transform(String original) {
+        return super.transform(
+            original
+            .replace("Tool chain 'clang' (Clang)", toolChain.instanceDisplayName)
+            .replace("platform: current", "platform: " + NativePlatformsTestFixture.defaultPlatformName)
+            .replaceAll('(?m)(build/binaries/.+/)lib(\\w+).dylib$') { it[1] + OperatingSystem.current().getSharedLibraryName(it[2]) }
+            .replaceAll('(?m)(build/binaries/.+/)lib(\\w+).a$') { it[1] + OperatingSystem.current().getStaticLibraryName(it[2]) }
+            .replaceAll('(?m)(build/binaries/.+/)(\\w+)$') { it[1] + OperatingSystem.current().getExecutableName(it[2]) }
+        )
+    }
+}
diff --git a/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/model/ConsoleReportOutput.groovy b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/model/ConsoleReportOutput.groovy
new file mode 100644
index 0000000..1ee788f
--- /dev/null
+++ b/subprojects/diagnostics/src/testFixtures/groovy/org/gradle/api/reporting/model/ConsoleReportOutput.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.model
+
+import org.gradle.util.TextUtil
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ConsoleReportOutput {
+    public static final String LINE_SEPARATOR = TextUtil.getPlatformLineSeparator()
+    public static final int HEADING_LINE_NUMBER = 2
+    public static final int FIRST_NODE_LINE_NUMBER = 6
+    public static final int PADDING_SIZE = 4
+    private String consoleOutput
+    def lines
+
+    ConsoleReportOutput(String consoleOutput) {
+        this.consoleOutput = toPlatformLineSeparators(consoleOutput)
+        lines = consoleOutput?.split(LINE_SEPARATOR)
+    }
+
+    void hasTitle(String text) {
+        lineIs(HEADING_LINE_NUMBER, '------------------------------------------------------------')
+        lineIs(HEADING_LINE_NUMBER + 1, text)
+        lineIs(HEADING_LINE_NUMBER + 2, '------------------------------------------------------------')
+    }
+
+    void hasNodeStructure(String text) {
+        List<String> nodeLines = lines[FIRST_NODE_LINE_NUMBER..-1]
+        def subject = toPlatformLineSeparators(text).split(LINE_SEPARATOR)
+        String firstToken = subject[0]
+        int startPosition = nodeLines.findIndexOf { name -> name == firstToken }
+        assert startPosition >= 0: "Failed to find the first node:$firstToken"
+        int endPosition = startPosition + (subject.size() - 1)
+        nodeLines[startPosition..endPosition].eachWithIndex { String line, i ->
+            assert line.startsWith(subject[i]): "\n\n Expected Line:|${line}| to start with:|${subject[i]}| \n\n"
+        }
+    }
+
+    void hasRootNode(String text) {
+        assert lines[FIRST_NODE_LINE_NUMBER] == text
+    }
+
+    void lineIs(int num, String text) {
+        assert lines[num] == text
+    }
+
+    void hasNodeAtDepth(String node, int depth) {
+        String paddedNode = ((" " * PADDING_SIZE) * (depth - 1)) + node
+        assert lines.findAll { it.startsWith(paddedNode) }
+    }
+
+    void debug() {
+        println("Total report lines: ${lines.size()}")
+        println("Original output: ${consoleOutput}")
+        println("Numbered Lines: ")
+        lines.eachWithIndex { l, i ->
+            println "$i. $l"
+        }
+
+    }
+}
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
index 30079d1..dd6df8e 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
@@ -30,7 +30,7 @@ class AllDistributionIntegrationSpec extends DistributionIntegrationSpec {
 
     @Override
     int getLibJarsCount() {
-        171
+        157
     }
 
     def allZipContents() {
@@ -56,7 +56,7 @@ class AllDistributionIntegrationSpec extends DistributionIntegrationSpec {
                 buildAndGradleDirs << it
             }
         }
-        buildAndGradleDirs.empty
+        buildAndGradleDirs == []
 
         // Javadoc
         contentsDir.file('docs/javadoc/index.html').assertIsFile()
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
index ed47d7f..707af73 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
@@ -27,7 +27,7 @@ class BinDistributionIntegrationSpec extends DistributionIntegrationSpec {
 
     @Override
     int getLibJarsCount() {
-        171
+        157
     }
 
     def binZipContents() {
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
index f1df5ab..2b1a9ec 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
@@ -114,7 +114,7 @@ abstract class DistributionIntegrationSpec extends AbstractIntegrationSpec {
 
         def toolingApiJar = contentsDir.file("lib/gradle-tooling-api-${version}.jar")
         toolingApiJar.assertIsFile()
-        assert toolingApiJar.length() < 240 * 1024; // tooling api jar is the small plain tooling api jar version and not the fat jar.
+        assert toolingApiJar.length() < 300 * 1024; // tooling api jar is the small plain tooling api jar version and not the fat jar.
 
         // Plugins
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-dependency-management-${version}.jar"))
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index 88bf81a..933c787 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -364,7 +364,8 @@ task javadocAll(type: Javadoc) {
 task configureGroovydocAll {
     doFirst {
         project.configure(groovydocAll) {
-            [javaApiUrl, groovyApiUrl].each {
+            [javaApiUrl].each {
+//            [javaApiUrl, groovyApiUrl].each {
                 link(it, *(new URL("$it/package-list").text.tokenize("\n")))
             }
             docTitle = "Gradle API $version"
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
index 9d7061a..1d27a05 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
@@ -86,6 +86,9 @@
             <tr>
                 <td>getTaskDependencyFromProjectDependency</td>
             </tr>
+            <tr>
+                <td>defaultDependencies</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.DependencySubstitutions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.DependencySubstitutions.xml
new file mode 100644
index 0000000..94c9839
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.DependencySubstitutions.xml
@@ -0,0 +1,34 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>all</td>
+            </tr>
+            <tr>
+                <td>module</td>
+            </tr>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>substitute</td>
+            </tr>
+        </table>
+    </section>
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
index a5cb5c2..dccb7b7 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
@@ -13,6 +13,9 @@
             <tr>
                 <td>componentSelection</td>
             </tr>
+            <tr>
+                <td>dependencySubstitution</td>
+            </tr>
         </table>
     </section>
     <section>
@@ -39,8 +42,11 @@
                 <td>eachDependency</td>
             </tr>
             <tr>
+                <td>dependencySubstitution</td>
+            </tr>
+            <tr>
                 <td>componentSelection</td>
             </tr>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml
index 985723c..87ab3ae 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml
@@ -52,6 +52,10 @@
                 <td>keepStubs</td>
                 <td><literal>false</literal></td>
             </tr>
+            <tr>
+                <td>javaAnnotationProcessing</td>
+                <td><literal>false</literal></td>
+            </tr>
         </table>
     </section>
     <section>
@@ -67,4 +71,4 @@
             </tr>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.DependentSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.DependentSourceSet.xml
deleted file mode 100644
index b5dc4f9..0000000
--- a/subprojects/docs/src/docs/dsl/org.gradle.language.DependentSourceSet.xml
+++ /dev/null
@@ -1,44 +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.
-  -->
-
-<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>
-        </table>
-    </section>
-</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.DependentSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.DependentSourceSet.xml
new file mode 100644
index 0000000..5a89a52
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.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>
+            <tr>
+                <td>preCompiledHeader</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.language.HeaderExportingSourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.HeaderExportingSourceSet.xml
similarity index 100%
rename from subprojects/docs/src/docs/dsl/org.gradle.language.HeaderExportingSourceSet.xml
rename to subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.HeaderExportingSourceSet.xml
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask.xml
new file mode 100644
index 0000000..19845eb
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2015 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
index b70a237..613587c 100755
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -74,7 +74,6 @@
         <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="projectComponents" extensionClass="org.gradle.platform.base.ComponentSpecContainer"/>
         <extends targetClass="org.gradle.api.Project" id="binaries" extensionClass="org.gradle.platform.base.BinaryContainer"/>
         <extends targetClass="org.gradle.api.Project" id="sources" extensionClass="org.gradle.language.base.ProjectSourceSet"/>
     </plugin>
diff --git a/subprojects/docs/src/docs/release/notes-next.md b/subprojects/docs/src/docs/release/notes-next.md
deleted file mode 100644
index 30ffb2a..0000000
--- a/subprojects/docs/src/docs/release/notes-next.md
+++ /dev/null
@@ -1,113 +0,0 @@
-## New and noteworthy
-
-Here are the new features introduced in this Gradle release.
-
-### Google Test support (i)
-
-- TBD
-
-### Dependency substitution accepts projects
-
-You can now replace an external dependency with a project dependency. The `DependencyResolveDetails` object
-allows access to the `ComponentSelector` as well:
-
-    resolutionStrategy {
-        eachDependency { details ->
-            if (details.selector instanceof ModuleComponentSelector && details.selector.group == 'com.example' && details.selector.module == 'my-module') {
-                useTarget project(":my-module")
-            }
-        }
-    }
-### Dependency substitution rules
-
-In previous Gradle versions you could replace an external dependency with another like this:
-
-    resolutionStrategy {
-        eachDependency { details ->
-            if (details.requested.group == 'com.example' && details.requested.module == 'my-module') {
-                useVersion '1.3'
-            }
-        }
-    }
-
-This behaviour has been enhanced and extended, with the introduction of 'Dependency Substitution Rules'.
-These rules allow an external dependency to be replaced with a project dependency, and vice-versa. 
-
-You replace a project dependency with an external dependency like this:
-
-    resolutionStrategy {
-        dependencySubstitution {
-            withProject(project(":api")) { 
-                useTarget group: "org.utils", name: "api", version: "1.3" 
-            }
-        }
-    }
-
-And replace an external dependency with an project dependency like this:
-
-
-    resolutionStrategy {
-        dependencySubstitution {
-            withModule("com.example:my-module") {
-                useTarget project(":project1")  
-            }
-        }
-    }
-
-There are other options available to match module and project dependencies:
-
-    all { DependencySubstitution<ComponentSelector> details -> /* ... */ }
-    eachModule() { ModuleDependencySubstitution details -> /* ... */ }
-    withModule("com.example:my-module") { ModuleDependencySubstitution details -> /* ... */ }
-    eachProject() { ProjectDependencySubstitution details -> /* ... */ }
-    withProject(project(":api)) { ProjectDependencySubstitution details -> /* ... */ }
-
-It is also possible to replace one project dependency with another, or one external dependency with another. (The latter provides the same functionality
-as `eachDependency`).
-Note that the `ModuleDependencySubstitution` has a convenience `useVersion()` method. For the other substitutions you should use `useTarget()`.
-
-## Promoted features
-
-Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to backwards compatibility.
-See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
-
-The following are the features that have been promoted in this Gradle release.
-
-<!--
-### Example promoted
--->
-
-## Fixed issues
-
-## Deprecations
-
-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 3.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
-
-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).
-
-<!--
-### Example deprecation
--->
-
-### Changing a configuration after it has been resolved
-
-TODO
-
-## Potential breaking changes
-
-<!--
-### Example breaking change
--->
-
-## External contributions
-
-We would like to thank the following community members for making contributions to this release of Gradle.
-
-* [Lorant Pinter](https://github.com/lptr), [Daniel Vigovszky](https://github.com/vigoo) and [Mark Vujevits](https://github.com/vujevits) - implement dependency substitution for projects
-
-We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
-
-## Known issues
-
-Known issues are problems that were discovered post release that are directly related to changes made in this release.
diff --git a/subprojects/docs/src/docs/release/notes-template.md b/subprojects/docs/src/docs/release/notes-template.md
index 6ec04ab..240506b 100644
--- a/subprojects/docs/src/docs/release/notes-template.md
+++ b/subprojects/docs/src/docs/release/notes-template.md
@@ -28,7 +28,7 @@ The following are the features that have been promoted in this Gradle release.
 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 3.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
 
-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).
+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://discuss.gradle.org).
 
 <!--
 ### Example deprecation
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index 4fe803e..95d64a9 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,225 +1,207 @@
-The big story for Gradle 2.4 is the improved performance.
-While it's not unusual for a new Gradle release to be the fastest Gradle yet, Gradle 2.4 is _significantly_ faster.
-Many early testers of Gradle 2.4 have reported that overall build times have improved by 20% up to 40%.
+Gradle 2.5 delivers some big features and plenty of internal improvements and optimizations.
 
-There are two main components to the improved performance; [general configuration time optimizations](#significant-configuration-time-performance-improvements),
-and the [class reuse with the Gradle Daemon](#improved-performance-of-gradle-daemon-via-class-reuse).
-As such, using the [Gradle Daemon](userguide/gradle_daemon.html) now provides an even greater performance advantage.
+The new “Continuous build” support brings capability to have Gradle automatically initiate a build in response to file system changes.
+This works with any build and has many applications. For example, it can be used to get continuous compile and test, giving more immediate feedback.
+As it works with _any_ Gradle task, it is equally applicable to documentation generation tasks and more.
+Upcoming versions of Gradle will build on this capability to facilitate reloading applications and the like under development in response to changes,
+providing a fast feedback local development loop.
 
-If you are using Gradle to build native code (e.g. C/C++), the news gets even better with the introduction of [parallel compilation](#parallel-native-compilation).
+“Dependency substitution rules” make it possible to develop multiple related projects builds with more flexibility.
+It is now possible to dynamically substitute source dependencies with pre-built versions, and vice-versa, with greater ease.
+Dependency substitution rules are a powerful new addition to Gradle's arsenal for Dependency Management.
 
-Memory consumption has also been reduced when compiling Java source code with Java 7 and 8, which can significantly improve compile times for large Java projects.
+The Tooling API (i.e. the mechanism for [embedding Gradle](userguide/embedding.html)) now provides rich event information during the execution of the build to specified listeners.
+This mechanism can be used by IDEs and other tools wanting to provide visualisations or extra progress information during build execution.
 
-Other highlights include [support for Amazon S3 dependency repositories](#support-for-amazon-web-services-s3-backed-repositories) 
-and [support for using annotation processors with Groovy code](#support-for-“annotation-processing”-of-groovy-code) and more. 
+There are also notable improvements to Gradle's support for native code, including support for [GoogleTest](https://code.google.com/p/googletest/) and precompiled headers.
 
 ## New and noteworthy
 
 Here are the new features introduced in this Gradle release.
 
-### Significant configuration time performance improvements
+### Continuous build (i)
 
-Gradle 2.4 features a collection of performance improvements particularly targeted at “configuration time”
-(i.e. the part of the build lifecycle where Gradle is comprehending the definition of the build by executing build scripts and plugins).
-Several users of early Gradle 2.4 builds have reported build time improvements of around 20% just by upgrading to Gradle 2.4.
+The new continuous build support allows Gradle to automatically start building in response to file system changes.
+When you run with the `--continuous` or `-t` command line options, Gradle will not exit at the end of a build.
+Instead, Gradle will wait for files that are processed by the build to change.
+When changes are detected, Gradle will re-run the previous build with the same task selection.
 
-Most performance improvements were realized by optimizing internal algorithms along with data and caching structures.
-Builds that have more configuration (i.e. more projects, more build scripts, more plugins, larger build scripts) stand to gain more from the improvements.
-The Gradle build itself, which is of non trivial complexity, realized improved configuration times of 34%.
-Stress tests run as part of Gradle's own build pipeline have demonstrated an 80% improvement in configuration time with Gradle 2.4.
+For instance, if you run `gradle -t build` in a typical Java project, main and test sources will be built and tests will be run.
+If changes are made to the project's main sources, Gradle will rebuild the main Java sources and re-run the project's tests.
+If changes are made to the project's test sources, Gradle will only rebuild the test Java sources and re-run the project's tests.
 
-No change is required to builds to leverage the performance improvements.
+This is just the initial iteration of this feature, which will improve in coming releases.
+Future releases will increase the scope of “changes” to include more than just local input files.
+For example, the scope of considered “changes” may expand in the future to encompass dependencies in remote repositories along with other types of inputs that affect the build result.
 
-### Improved performance of Gradle Daemon via class reuse
+For more information, please see the [new User Guide chapter](userguide/continuous_build.html).
 
-The [Gradle Daemon](userguide/gradle_daemon.html) is now much smarter about reusing classes across builds.
-This makes all Gradle builds faster when using the Daemon, and builds that use non-core plugins in particular.
-This feature is completely transparent and applies to all builds.
+### Dependency substitution rules (i)
 
-The Daemon is a persistent process.
-For a long time it has reused the Gradle core infrastructure and plugins across builds.
-This allows these classes to be loaded _once_ during a “session”, instead of for each build (as is the case when not using the Daemon).
-The level of class reuse has been greatly improved in Gradle 2.4 to also cover build scripts and third-party plugins.
-This improves performance in several ways.
-Class loading is expensive and by reusing classes this just happens less.
-Classes also reside in memory and with the Daemon being a persistent process reuse also reduces memory usage.
-This also reduces the severity of class loader leaks (because fewer class loaders actually leak) which again reduces memory usage.
+Previous versions of Gradle allowed an external dependency to be replaced with another using a ['Dependency Resolve Rule'](https://docs.gradle.org/1.4/release-notes#dependency-resolve-rules).
+With the introduction of 'Dependency substitution rules' this behaviour has been enhanced and extended to allow external dependencies and project dependencies to be replaced interchangeably.
 
-Perhaps more subtly, reusing classes across builds also improves performance by giving the JVM more opportunity to optimize the code.
-The optimizer typically improves build performance _dramatically_ over the first half dozen builds in a JVM.
+When combined with a configurable Gradle settings file, these dependency substitution rules permit some powerful new ways of working with Gradle:
 
-The [Tooling API](userguide/embedding.html), which allows Gradle to be embedded in IDEs automatically uses the Gradle Daemon.
-The Gradle integration in IDEs such as Android Studio, Eclipse, IntelliJ IDEA and NetBeans also benefits from these performance improvements.
+* While developing a patch for an external library, use a local project dependency instead of a module dependency.
+* For a large multi-project build, only develop with a subset of the projects locally, downloading the rest from an external repository.
+* Extensions to Gradle such as [Prezi Pride](https://github.com/prezi/pride) no longer require a custom dependency syntax.
 
-If you aren't using the [Gradle Daemon](userguide/gradle_daemon.html), we urge you to try it out with Gradle 2.4.
+Substitute a project dependency with an external module dependency like this:
 
-### Parallel native compilation
+    resolutionStrategy {
+        dependencySubstitution {
+            substitute project(":api") with module("org.utils:api:1.3")
+        }
+    }
 
-Starting with 2.4, Gradle uses multiple concurrent compilation processes when compiling C/C++/Objective-C/Objective-C++/Assembler languages. 
-This is automatically enabled for all builds and works for all supported compilers (GCC, Clang, Visual C++). 
-Up until this release, Gradle compiled all native source files sequentially.
+Alternatively, an external dependency can be replaced with a project dependency like this:
 
-This change has dramatic performance implications for native builds.
-Benchmarks for a project with a 500 source files on a machine with 8 processing cores available exhibited reduced build times of 53.4s to 12.9s. 
+    resolutionStrategy {
+        dependencySubstitution {
+            substitute module("org.utils:api") with project(":api")
+        }
+    }
 
-The degree of concurrency is determined by the new [“max workers” build setting](#new-“max-workers”-build-setting).
+It is also possible to substitute a project dependency with another, or a module dependency with another.
+(The latter provides the same functionality as `eachDependency`, with a more convenient syntax).
 
-### New “max workers” build setting (i)
+Note: adding a dependency substitution rule to a `Configuration` used as a task input changes the timing of when that configuration is resolved.
+Instead of being resolved on first use, the `Configuration` is instead resolved when the task graph is being constructed.
+This can have unexpected consequences if the configuration is being further modified during task execution,
+or if the configuration relies on modules that are published during execution of another task.
 
-The new `--max-workers=«N»` command line switch, and synonymous `org.gradle.workers.max=«N»` build property (e.g. specified in `gradle.properties`) determines the degree of build concurrency.
+For more information consult the [User Guide](userguide/dependency_management.html#dependency_substitution_rules)
+and the [DSL Reference](dsl/org.gradle.api.artifacts.DependencySubstitutions.html).
 
-As of Gradle 2.4, this setting influences [native code compilation](#parallel-native-compilation) and [parallel project execution](userguide/multi_project_builds.html#sec:parallel_execution).
-The “max workers” setting specifies the size of these _independent_ worker pools.
-However, a single worker pool is used for all native compilation operations.
-This means that if two (or more) native compilation tasks are executing at the same time, 
-they will share the worker pool and the total number of concurrent compilation options will not exceed the “max workers” setting.
+This feature was contributed by [Lóránt Pintér](https://github.com/lptr) and the team at [Prezi](https://prezi.com).
 
-Future versions of Gradle will leverage the shared worker pool for more concurrent work, allowing more precise control over the total build concurrency.
+### Progress events via the Tooling API (i)
 
-The default value is the number of processors available to the build JVM (as reported by 
-[`Runtime.availableProcessors()`](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors\(\))).
-Alternatively, it can be set via the `--max-workers=«N»` command line switch or `org.gradle.workers.max=«N»` build property where `«N»` is a positive, non-zero, number.
+It is now possible to receive progress events for various build operations.
+Listeners can be provided to the [`BuildLauncher`](javadoc/org/gradle/tooling/BuildLauncher.html) via the
+[`addProgressListener(ProgressListener)`](javadoc/org/gradle/tooling/LongRunningOperation.html#addProgressListener\(org.gradle.tooling.events.ProgressListener\)) method.
+Events are fired when the task graph is being populated, when each task is executed, when tests are executed, etc.
 
-Please note: the `--parallel-threads` command line switch [has been deprecated](#setting-number-of-build-execution-threads-with---parallel-threads) in favor of this new setting.
+All operations are part of a single-root hierarchy that can be traversed through the operation descriptors via
+[`ProgressEvent.getDescriptor()`](javadoc/org/gradle/tooling/events/ProgressEvent.html#getDescriptor\(\)) and
+[`OperationDescriptor.getParent()`](javadoc/org/gradle/tooling/events/OperationDescriptor.html#getParent\(\)).
 
-### Reduced memory consumption when compiling Java source code with Java 7 and 8
+If you are only interested in the progress events for a sub-set of all available operations, you can use
+[`LongRunningOperation.addProgressListener(ProgressListener, Set<OperationType>)`](javadoc/org/gradle/tooling/LongRunningOperation.html#addProgressListener\(org.gradle.tooling.events.ProgressListener,%20java.util.Set\)).
+For example, you may elect to only receive events for the execution of tasks.
 
-By working around JDK bug [JDK-7177211](https://bugs.openjdk.java.net/browse/JDK-7177211), Java compilation requires less memory in Gradle 2.4.
-This JDK bug causes what was intended to be a performance improvement to not improve compilation performance and use more memory.
-The workaround is to implicitly apply the internal compiler flag `-XDuseUnsharedTable=true` to all compilation operations.
+One potential use of this new capability would be to provide interesting visualisations of the build execution.
 
-Very large Java projects (building with Java 7 or 8) may notice dramatically improved build times due to the decreased memory throughput which in turn
-requires less aggressive garbage collection in the build process.
+### Simpler default dependencies (i)
 
-### Support for Amazon Web Services S3 backed repositories (i)
+Many Gradle plugins allow the user to specify a dependency for a particular tool, supplying a default version only if none is provided by the user.
+A common implementation of this involves using a `beforeResolve()` hook to check if the configuration has any dependencies, adding the default dependency if not.
+The new [`defaultDependencies()`](dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:defaultDependencies\(org.gradle.api.Action\))
+method has been introduced to make this simpler and more robust.
 
-It is now possible to consume dependencies from, and publish to, [Amazon Web Services S3](http://aws.amazon.com/s3) stores
-when using [`MavenArtifactRepository`](dsl/org.gradle.api.artifacts.repositories.MavenArtifactRepository.html) 
-or [`IvyArtifactRepository`](dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.html).
+The use of `beforeResolve()` to specify default dependencies will continue to work,
+but will emit a deprecation warning if the configuration has already participated in dependency resolution when it is first resolved (see below).
 
-    repositories {
-        maven {
-            url "s3://someS3Bucket/maven2"
-            credentials(AwsCredentials) {
-                accessKey "someKey"
-                secretKey "someSecret"
-            }
-        }
+Specifying a default dependency with `beforeResolve` (deprecated):
 
-        ivy {
-            url "s3://someS3Bucket/ivy"
-            credentials(AwsCredentials) {
-                accessKey "someKey"
-                secretKey "someSecret"
-            }
+    def util = dependencies.create("org.gradle:my-util:1.0")
+    conf.incoming.beforeResolve {
+        if (conf.dependencies.empty) {
+            conf.dependencies.add(util)
         }
     }
 
-Downloading dependencies from S3 is supported for Maven and Ivy type repositories as above.
-
-Publishing to S3 is supported with both the [`ivy-publish`](userguide/publishing_ivy.html) and [`maven-publish`](userguide/publishing_maven.html) plugins, 
-as well as when using an [`IvyArtifactRepository`](dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.html) with an [`Upload`](dsl/org.gradle.api.tasks.Upload.html) task
-(see section [8.6. Publishing artifacts](userguide/artifact_dependencies_tutorial.html#N10669) of the User Guide).
-
-Please see section [50.6. Repositories](userguide/dependency_management.html#sec:repositories) of the User Guide for more information on configuring S3 repository access.
-
-This feature was contributed by [Adrian Kelly](https://github.com/adrianbk).
-
-### Support for publishing to Maven repositories over SFTP using `maven-publish` plugin
-
-In previous releases, it was not possible to publish to a Maven repository (via the [`maven-publish` plugin](userguide/publishing_maven.html))
-via SFTP in the same manner that it was for an Ivy repository or when downloading dependencies.
-This restriction has been lifted, with the publishing now supporting all of the transport protocols that Gradle currently supports (`file`, `http(s)`, `sftp` and `s3`).
-
-This change will also make it possible to seamlessly use any transports that Gradle will support in the future at that time.
-
-Please see section [50.6. Repositories](userguide/dependency_management.html#sec:repositories) of the User Guide for more information on configuring SFTP repository access.
+Specifying a default dependency with `defaultDependencies` (recommended):
 
-### Depending on a particular Maven snapshot version
-
-It is now possible to depend on particular Maven snapshot, rather than just the “latest” published version.
-
-    dependencies {
-      compile "org.company:my-lib:1.0.0-20150102.010203-20"
+    def util = dependencies.create("org.gradle:my-util:1.0")
+    conf.defaultDependencies { dependencies ->
+        dependencies.add(util)
     }
-    
-The Maven snapshot version number is a timestamp and snapshot number.
-The snippet above is depending on the snapshot of version `1.0.0` published on the 2nd of January 2015, at 01:02:03 AM which was the 20th snapshot published.
-
-This feature was contributed by [Noam Y. Tenne](https://github.com/noamt).
-
-### Support for “annotation processing” of Groovy code
 
-It is now possible to use Java's [“annotation processing”](https://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Processor.html) with Groovy code.
-This, for example, allows using the [Dagger](http://square.github.io/dagger) dependency injection library, that relies on annotation processing, with Groovy code.
+See the <a href="dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:defaultDependencies(org.gradle.api.Action)">DSL reference</a> for more details.
 
-Annotation processing is a Java centric feature.
-Support for Groovy is achieved by having annotation processors process the Java “stubs” that are generated from Groovy code.
-The stubs convey the structure of the class, which is typically used to allow Java code to compile against the Groovy code in “one pass”.
-Annotations on structural elements (i.e. classes/methods/fields) will be present in the generated stubs.
-Annotation processors will detect such annotations on stubs as they would with “normal” Java code.
+### Precompiled header support (i)
 
-The support for annotation processing of Groovy code is limited to annotation processors that generate new classes, and not to processors that modify annotated classes.
-The official and supported annotation processing mechanisms _do not_ support modifying classes, so almost all annotation processors will work.
-However, some popular annotation processing tools, notably [Project Lombok](http://projectlombok.org), that use unofficial API to modify classes will not work.
+Precompiled headers are a performance optimization for native builds that allows commonly used headers to be parsed only once rather than for each file that includes the headers.
+Precompiled headers are now supported for C, C++, Objective-C and Objective-C++ projects.
 
-This feature was contributed by [Will Erickson](https://github.com/Sarev0k).
+To use a precompiled header, a header file needs to created containing all of the headers that should be precompiled.
+This header file is then declared in the build script as a precompiled header.
 
-### Generate wrapper with specific version from command-line
+    model {
+        components {
+            hello(NativeLibrarySpec) {
+                sources {
+                    cpp {
+                        preCompiledHeader "pch.h"
+                    }
+                }
+            }
+        }
+    }
 
-Previously to generate a Gradle wrapper with a specific version, or a custom distribution URL,
-you had to change the `build.gradle` file to contain a wrapper task with a configured `gradleVersion` property.
+Each source set can have a single precompiled header defined.
+Any source file that includes this header file as the first header will be compiled using the precompiled header.
+Otherwise, the precompiled header will be ignored and the source file will be compiled in the normal manner.
 
-Now the target Gradle version or the distribution URL can be configured from the command-line, without having
-to add or modify the task in `build.gradle`:
+Please see the [User Guide](userguide/nativeBinaries.html#native_binaries:preCompiledHeaders) for further information.
 
-<pre><tt>gradle wrapper --gradle-version 2.3</tt></pre>
+### Google Test support (i)
 
-And with a distribution URL:
+The new Gradle `google-test` plugin provides support for compiling and executing [GoogleTest](https://code.google.com/p/googletest/) tests in your native binary project.
+You simply need to include your Google Test test sources in a conventional location (i.e. `src/«component name»Test/cpp`).
 
-<pre><tt>gradle wrapper --gradle-distribution-url https://myEnterpriseRepository:7070/gradle/distributions/gradle-2.3-bin.zip</tt></pre>
+Gradle will build a test executable and run your tests.
 
-This feature was contributed by [Lóránt Pintér](https://github.com/lptr).
+<pre><tt>> gradle -q runFailingOperatorsTest
 
-### Customization of application plugin start script generation (i)
+[==========] Running 2 tests from 1 test case.
+[----------] Global test environment set-up.
+[----------] 2 tests from OperatorTests
+[ RUN      ] OperatorTests.test_plus
+src/operatorsTest/cpp/test_plus.cpp:8: Failure
+Value of: plus(0, -2) == -2
+ Actual: false
+Expected: true
+[  FAILED  ] OperatorTests.test_plus (0 ms)
+[ RUN      ] OperatorTests.test_minus
+[       OK ] OperatorTests.test_minus (0 ms)
+[----------] 2 tests from OperatorTests (0 ms total)
 
-The [application plugin](userguide/application_plugin.html) can be used to create “executable” distributions Java-based application, including operating system specific start scripts.
-While certain values in the generated scripts (e.g. main class name, classpath) were customizable, the script content was generally hardcoded and cumbersome to change.
-With Gradle 2.4, it is now much easier to fully customise the start scripts.
+[----------] Global test environment tear-down
+[==========] 2 tests from 1 test case ran. (0 ms total)
+[  PASSED  ] 1 test.
+[  FAILED  ] 1 test, listed below:
+[  FAILED  ] OperatorTests.test_plus
 
-The generation of the scripts is performed by a [`CreateStartScripts`](dsl/org.gradle.jvm.application.tasks.CreateStartScripts.html) task.
-Please consult its [DSL reference](dsl/org.gradle.jvm.application.tasks.CreateStartScripts.html) for customization examples.
+1 FAILED TEST</tt></pre>
 
-### Tooling API improvements (i)
+See the [User Guide](userguide/nativeBinaries.html#native_binaries:google_test) to learn more.
+Expect deeper integration with Google Test (and other native testing tools) in the future.
 
-The tooling API allows Gradle to be embedded in other applications, such as an IDE. This release includes a number of improvements to the tooling API:
+This feature was contributed by [Daniel Lacasse](https://github.com/Shad0w1nk).
 
-An application can now receive test progress events as a build is executed. Using these events, an application can display or report on test execution
-as tests run during the build.
+### Obtaining a task's group via the Tooling API
 
-An application receives test events using the
-[`LongRunningOperation.addTestProgressListener()`](javadoc/org/gradle/tooling/LongRunningOperation.html#addTestProgressListener-org.gradle.tooling.events.test.TestProgressListener-) method.
+It is now possible to obtain the “group” of a task via [`org.gradle.tooling.model.Task.getGroup()`](javadoc/org/gradle/tooling/model/GradleTask.html#getGroup\(\)).
 
-The following additions have been added to the respective [Tooling API](userguide/embedding.html) models:
+### New managed model improvements
 
-* [`GradleProject.getProjectDirectory()`](javadoc/org/gradle/tooling/model/GradleProject.html#getProjectDirectory--) returns the project directory of the project.
-* [`GradleEnvironment.getGradleUserHome()`](javadoc/org/gradle/tooling/model/build/GradleEnvironment.html#getGradleUserHome--)
-returns the effective Gradle user home directory that will be used for operations performed through the Tooling API.
+A number of improvements have been made to the experimental managed model feature. This feature is currently used by the native plugins and will evolve into
+a general purpose feature for describing Gradle builds:
 
-### Rule based model configuration improvements 
+The model report for [Rule based model configuration](userguide/new_model.html) has been enhanced to display string representations of some values.
+This allows the effective values of the build model to be visualised, not just the structure as was the case previously.
 
-A number of improvements have been made to the rule based model configuration used by the native language plugins in this release.
-There is also a [new User Guide chapter that explains just what “rule based model configuration” is](userguide/new_model.html).
+More of the new software model is now visible to the model report and to configuration rules.
 
-Key additions and improvements in Gradle 2.4:
+[`@Managed`](javadoc/org/gradle/model/Managed.html) models can now have managed model properties of type `java.io.File`.
 
-- A basic [model report](userguide/new_model.html#N17A51) task that visualizes the model space for a project.
-- Addition of [`@Defaults`](javadoc/org/gradle/model/Defaults.html) and [`@Validate`](javadoc/org/gradle/model/Validate.html) lifecycle rules.
-- Additions to the API of [`CollectionBuilder`](javadoc/org/gradle/model/collection/CollectionBuilder.html) and 
-[`ManagedSet`](javadoc/org/gradle/model/collection/ManagedSet.html) that allow rules to be applied to all elements in the collection, 
-or to a particular element, or all elements of a given type.
+[`@Managed`](javadoc/org/gradle/model/Managed.html) types can now implement the [`Named`](javadoc/org/gradle/api/Named.html) interface.
+The `name` property will be automatically populated based on the objects location in the model graph.
 
-It is now also possible to [create model elements via the DSL](userguide/new_model.html#N17A0F).
+It is now possible for [`@Managed`](javadoc/org/gradle/model/Managed.html) types to declare properties of type [`ModelMap<T>`](javadoc/org/gradle/model/ModelMap.html).
 
 ## Fixed issues
 
@@ -230,234 +212,138 @@ in the next major Gradle version (Gradle 3.0). See the User guide section on the
 
 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://discuss.gradle.org).
 
-### Setting number of build execution threads with `--parallel-threads`
+### Changing a configuration after it has participated in dependency resolution
 
-The, incubating, `--parallel-threads` command line switch has been superseded by the new `--max-workers` command line switch and synonymous `org.gradle.workers.max` build property.
-Likewise, the [`StartParameter.getParallelThreadCount()`](javadoc/org/gradle/StartParameter.html#getParallelThreadCount\(\)) has also been deprecated.
+Unexpected behaviour can result from changing a configuration after it has participated in dependency resolution. Examples include:
 
-This setting configured [parallel project execution](userguide/multi_project_builds.html#sec:parallel_execution).
+* Change the dependencies of a parent of a configuration: the child configuration will get different dependencies depending on when
+  it is resolved.
+* Changing the artifacts of a configuration referenced as a project dependency: whether the referencing project gets those artifacts
+  depends on the order that configurations are resolved.
 
-The `--parallel-threads` is still respected, until removed.
-If not specified, the value specified for `--max-workers` will be used.
+Previous versions of Gradle prevented the resolved configuration itself from being modified, but did nothing to
+prevent modifications to related configurations after they have participated in dependency resolution. This version of Gradle extends
+the checks, emitting a deprecation warning when a modification is made to a configuration that has been referenced in dependency
+resolution.
 
-If you were using an invocation such as:
+One exception is that changes to the `ResolutionStrategy` of a configuration can be made at any time until that configuration is
+itself resolved. Changes to the strategy do not impact the resolution of child configurations, or configurations referenced as
+project dependencies. Thus, these changes are safe to make.
 
-<pre><tt>./gradlew build --parallel-threads=4</tt></pre>
+### Distribution Plugin changes
 
-The replacement is now:
+Due to a bug in the distribution plugin (see GRADLE-3278), earlier Gradle versions didn't follow the general naming convention for the assemble task of the main distribution.
+This has been fixed and assemble task name for the main distribution has changed from `assembleMainDist` to `assembleDist`.
 
-<pre><tt>./gradlew build --max-workers=4 --parallel</tt></pre>
+### Deprecation of `CollectionBuilder` and `ManagedSet`.
 
-Alternatively, the following can be used, which will use the default value for `--max-workers`:
+The types `org.gradle.model.collection.CollectionBuilder` and `org.gradle.model.collection.ManagedSet` have been deprecated and replaced by
+[`org.gradle.model.ModelMap`](javadoc/org/gradle/model/ModelMap.html) and [`org.gradle.model.ModelSet`](javadoc/org/gradle/model/ModelSet.html) respectively.
 
-<pre><tt>./gradlew build --parallel</tt></pre>
+As these types were incubating, they will be removed before Gradle 3.0.
+Please change your usage to the new `ModelMap` and `ModelSet` types.
 
-### Lifecycle plugin changes
+### Deprecations in Eclipse model
+As part of the changes in the IDE classpath generation, the following properties have been deprecated:
 
-The tasks `build`, `clean`, `assemble` and `check` are part of the standard build lifecycle and are added by most plugins, typically implicitly through the `base` or `language-base` plugins.
-Due to the way these tasks are implemented, it is possible to redefine them simply by creating your own task of the same name.
-This behavior has been deprecated and will not be supported in Gradle 3.0.
-That is, attempting to define a task with the same name as one of these lifecycle tasks when they are present will become an error just like any other attempt to create a task with the same name as an existing task.
+- `EclipseClasspath#noExportConfigurations`
+- `ProjectDependency#declaredConfigurationName`
+- `AbstractLibrary#declaredConfigurationName`
 
 ## Potential breaking changes
 
-### Incompatibility with Team City 9.0.3 and earlier
-
-An internal change to Gradle test execution has broken the Gradle integration provided by [JetBrains' TeamCity integration server](https://www.jetbrains.com/teamcity/).
-JetBrains have acknowledged the issue and have fixed the problem for the pending 9.0.4 release of TeamCity.
-
-Please see [https://youtrack.jetbrains.com/issue/TW-40615](https://youtrack.jetbrains.com/issue/TW-40615) for more information on the problem.
-
-If you are affected by this issue, you can install a pre-release version of the Gradle plugin for TeamCity which is available via the above link.
-
-### Class reuse across builds when using the Daemon
-
-Due to the [performance improvements enabled by reusing classes across builds with the Daemon](#improved-performance-of-gradle-daemon-via-class-reuse), 
-builds that equate static state to build state may behave differently with Gradle 2.4.
-
-Previously, classes were not reused across builds.
-This meant that each build created a new class object and therefore fresh static class state.
-It was therefore theoretically possible to use static state of classes loaded during the build as a kind of “build state”,
-which was reset for each build.
-This was never a supported feature or recommended technique.
-
-Now, due to classes being reused this is no longer possible.
-Class objects are reused across builds, including their static state.
+### Changes to the new software model
 
-The recommended way of achieving “build state” is to use a root project extension, or extra property.
+As work continues on the new software model for Gradle, many changes to the new incubating plugins and types
+are required. These changes should have no impact on builds that do not leverage these new features.
 
-### Model DSL changes
+#### Removal of `componentSpec` project extension
 
-There have been some changes to the behaviour of the `model { ... }` block:
+As part of work on exposing more of the component model as managed types, the `componentSpec` project extension previously added by all language plugins via `ComponentModelBasePlugin` has been removed.
+The `components` container can now only be accessed using configuration rules.
 
-- The `tasks` container now delegates to a `CollectionBuilder<Task>` instead of a `TaskContainer`.
-- The `components` container now delegates to a `CollectionBuilder<ComponentSpec>` instead of a `ComponentSpecContainer`.
-- The `binaries` container now delegates to a `CollectionBuilder<BinarySpec>` instead of a `BinaryContainer`.
+#### Changes to `ComponentSpecContainer`
 
-Generally, the DSL should be the same, except:
+- `ComponentSpecContainer` no longer implements `ExtensiblePolymorphicDomainObjectContainer<ComponentSpec>`.
+- `ComponentSpecContainer` now implements `ModelMap<ComponentSpec>`.
+- All configuration done using subject of type `ComponentSpecContainer` is now deferred. In earlier versions of Gradle this configuration was eager.
 
-- Elements are not implicitly created. In particular, to define a task with default type, you need to use `model { tasks { myTask(Task) { ... } }`
-- Elements are not created or configured eagerly, but are configured as required.
-- The `create` method returns void.
-- The `withType()` method selects elements based on the public contract type rather than implementation type.
-- Using create syntax fails when the element already exists.
-- There are currently no query method on this interface.
+#### Changes to `ComponentSpec`
+- `getSource()` now returns a `ModelMap<LanguageSourceSet>` instead of `DomainObjectSet<LanguageSourceSet>`.
+- `sources()` now takes a `Action<? super ModelMap<LanguageSourceSet>>` instead of `Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>>`.
+- `getBinaries()` now returns a `ModelMap<BinarySpec>` instead of `DomainObjectSet<BinarySpec>`.
+- `binaries()` now takes a `Action<? super ModelMap<BinarySpec>>` instead of `Action<? super DomainObjectSet<BinarySpec>>`.
+- Source sets and binaries cannot be removed from these collections.
 
-### Updated default Scala Zinc compiler version
+#### Changes in `TestSuiteContainer`
 
-The default version of the Scala Zinc compiler has changed from 0.3.0 to 0.3.5.3.
+- `TestSuiteContainer` no longer implements `ExtensiblePolymorphicDomainObjectContainer<TestSuiteSpec>`.
+- `TestSuiteContainer` now implements `ModelMap<TestSuiteSpec>`.
+- All configuration done using subject of type `TestSuiteContainer` is now deferred. In earlier versions of Gradle this configuration was eager.
 
-### `MavenDeployer` no longer uses global Maven `settings.xml`
+### Maven publishing
 
-When publishing to a Maven repository using an `Upload` task (e.g. `uploadArchives`) and the `mavenDeployer` repository type,
-the global Maven `settings.xml` file is no longer consulted.
+The [maven-publish](userguide/publishing_maven.html) and [maven](userguide/maven_plugin.html) plugins
+no longer use the Maven 2 based [Maven ant tasks](https://maven.apache.org/ant-tasks/) libraries to publish artifacts.
+Both plugins now use the newer Maven 3 and Aether libraries.
+Whilst the API's exposed by both plugins remain unchanged, the underlying publishing libraries have been upgraded.
 
-Previously, the “mirror”, “authentication” and “proxy” settings defined in the global `settings.xml` file were effecting publishing,
-making builds less portable which is no longer the case.
+This upgrade was contributed by [Mikolaj Izdebski](https://github.com/mizdebsk).
 
-This resolves [GRADLE-2681].
+### Java annotation processing of Groovy code is now disabled by default
 
-The _user_ settings file (i.e. `~/.m2/settings.xml`) is still consulted for the location of the local repository when performing a local install
-(e.g. `gradle install` or `gradle publishToMavenLocal`).
+[Gradle 2.4 introduced the ability to use Java annotation processors on Groovy sources](https://docs.gradle.org/2.4/release-notes#support-for-“annotation-processing”-of-groovy-code).
+If annotation processors were present on the classpath, they were implicitly applied to Groovy source.
+This caused problems in situations where a separate “processing” strategy was required for joint compiled Groovy source (GRADLE-3300).
 
-### `PublishToMavenLocal.repository` property has been removed
+Java annotation processing of Groovy source is now disabled by default.
+It can be enabled by setting the
+[`javaAnnotationProcessing` property of `GroovyCompileOptions`](dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.html#org.gradle.api.tasks.compile.GroovyCompileOptions:javaAnnotationProcessing)
 
-Previously, the [`PublishToMavenLocal`](javadoc/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.html) task 
-(as used by the [`maven-publishing` plugin](userguide/publishing_maven.html)) could be configured with an `ArtifactRepository` instance, 
-which would specify the location to install to.
-The default repository was the repository that returned by
-[`RepositoryHandler.mavenLocal()`](dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html#org.gradle.api.artifacts.dsl.RepositoryHandler:mavenLocal\(\)).
-
-It is no longer possible to provide an arbitrary repository to this task.
-If you need to publish to an arbitrary repository, please use the [`PublishToMavenRepository`](javadoc/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.html) task type instead.
-
-### Changed default assembler executable for GCC/Clang tool chains
-
-The default tool used when turning assembler into object files is now `gcc` or `clang` instead of `as`.  
-Some arguments that were specific to `as` were converted to their GCC/Clang equivalents.  
-If you were passing arguments to the assembler, you may now need to use `-Wa` when passing them.  
-
-Please see [GCC's documentation](https://gcc.gnu.org/onlinedocs/gcc/Assembler-Options.html) for passing arguments to the assembler.
-
-### Changes to `CommandLineToolConfiguration.withArguments()` usage
-
-The [`CommandLineToolConfiguration`](javadoc/org/gradle/nativeplatform/toolchain/CommandLineToolConfiguration.html) allows fine grained configuration of a tool execution for the native domain,
-for example when configuring a [`GccPlatformToolChain`](javadoc/org/gradle/nativeplatform/toolchain/GccPlatformToolChain.html).
-There are changes to the semantics of the `withArguments()` method.
-
-The `withArguments()` method used to be called just before Gradle built the command-line arguments for the underlying tool for each source file.
-The arguments passed to this would include the path to the source file and output file. 
-This hook was intended to capture "overall" arguments to the command-line tool instead of "per-file" arguments. 
-Now, the `withArguments()` method is called once per task execution and does not contain any specific file arguments.  
-Any changes to arguments using this method will affect all source files.
-
-### Implicit Groovy source compilation while compiling build script is now disabled
-
-The Groovy compiler by default looks for dependencies in source form before looking for them in class form.
-That is, if Groovy code being compiled references `foo.bar.MyClass` then the compiler will look for `foo/bar/MyClass.groovy` on the classpath.
-If it finds such a file, it will try to compile it.
-If it doesn't it will then look for a corresponding class file.
-
-As of Gradle 2.4, this feature has been disabled for _build script_ compilation.
-It does not affect the compilation of “application” Groovy code (e.g. `src/main/groovy`).
-It has been disabled to make build script compilation faster.
-
-If you were relying on this feature, please use the [`buildSrc` feature](userguide/organizing_build_logic.html#sec:build_sources) as a replacement.
-
-### Changes to Groovy compilation when annotation processors are present
-
-When annotation processors are “present” for a Groovy compilation operation, all generated stubs are now compiled regardless of whether they are required or not.
-This change was required in order to have annotation processors process the stubs.
-Previously the stubs were made available to the Java code under compilation via the source path, which meant that only classes actually referenced by Java code were compiled.
-The implication is that more compilation is now required for Groovy code when annotation processors are present, which means longer compile times.
-
-This is unlikely to be noticeable unless the code base contains a lot of Groovy code.
-If this is problematic for your build, the solution is to separate the code that requires annotation processing from the code that does not to some degree.
-
-### Changes to default value for Java compilation `sourcepath`
-
-The source path indicates the location of source files that _may_ be compiled if necessary.
-It is effectively a complement to the class path, where the classes to be compiled against are in source form.
-It does __not__ indicate the actual primary source being compiled.
-
-The source path feature of the Java compiler is rarely needed for modern builds that use dependency management.
-
-The default value for the source path as of this release is now effectively an empty source path.
-Previously Gradle implicitly used the same default as the `javac` tool, which is the `-classpath` value.
-This causes unexpected build results when source accidentally ends up on the classpath, which can happen when dependencies surprisingly include source as well as binaries.
-
-This improvement was contributed by [Thomas Broyer](https://github.com/tbroyer).
-
-### Changes to `AuthenticationSupported.getCredentials()` method
-
-Due to the addition of support for [AWS S3 backed repositories](#support-for-amazon-web-services-s3-backed-repositories), some behavioral changes have been made to the
-[`AuthenticationSupported`](dsl/org.gradle.api.artifacts.repositories.AuthenticationSupported.html) interface, which is implemented by 
-[`MavenArtifactRepository`](dsl/org.gradle.api.artifacts.repositories.MavenArtifactRepository.html) and [`IvyArtifactRepository`](dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.html).
-
-The `getCredentials()` and `credentials(Action)` methods now throw an `IllegalStateException` if the configured credentials are not of type 
-[`PasswordCredentials`](javadoc/org/gradle/api/artifacts/repositories/PasswordCredentials.html), now that it is possible to use different types of credentials.
-
-### Changes to API of `AntlrTask`
-
-The [`AntlrTask`](dsl/org.gradle.api.plugins.antlr.AntlrTask.html) previous unnecessarily exposed the internal methods `buildArguments()` and `evaluateAntlrResult()`.
-
-These methods have been removed.
-
-### Updated libraries used by the Gradle API
-
-Some dependencies used in Gradle have been updated.
+    compileGroovy {
+        groovyOptions.javaAnnotationProcessing = true
+    }
 
-* **Slf4j** - 1.7.7 to 1.7.10
-* **Groovy** - 2.3.9 to 2.3.10
-* **Ant** - 1.9.3 to 1.9.4
+### Registering test progress listeners through the Tooling API
 
-These libraries are expected to be fully backwards compatible.
-It is expected that no Gradle builds will be negatively affected by these changes.
+The incubating API `BuildLauncher.addTestProgressListener()` and the associated listener type have been removed.
+This API has been superseded by the new [progress listener API](#progress-events-via-the-tooling-api).
 
-### Updated default tool versions for code quality plugins
+### Changes in IDE classpath generation
 
-The default version of the corresponding tool of the following code quality plugins have been updated:
+Project files generated by the Gradle Idea and Eclipse plugins are responsible for deriving the classpath from the declared list of dependencies in the build file.
+Some incorrect behavior, resulting in incorrect classpaths in the IDE, has been rectified in this Gradle version.
 
-* The `checkstyle` plugin now uses version 5.9 as default (was 5.7).
-   - The latest checkstyle version currently available is 6.4.1 but be aware that this version is not java 1.6 compliant
-   - Be aware that there is was a breaking change of the `LeftCurly` rule introduced in checkstyle 5.8 (see https://github.com/checkstyle/checkstyle/issues/247)
-* The `pmd` plugin now uses version 5.2.3 as default (was 5.1.1).
-* The `findbugs` plugin now uses version 3.0.1 as default (was 3.0.0).
-* The `codenarc` plugin now uses version 0.23 as default (was 0.21).
+Let's assume project A and B are part of a multi-project build.
+Project B declares a project dependency on project A.
+The generated classpath of project B is a union of the classpath of project A (the generated JAR file plus its dependencies) and its own declared top-level dependencies and transitive dependencies.
+In practice, this means that when project A and B depend on a specific library with different versions, the "exported" dependency version wins as it happens to be listed first in the classpath of project B.
+This behaviour might lead to compilation and runtime issues in the IDE as no conflict-resolution takes place across projects.
 
-### Deprecated ComponentMetadataHandler.eachComponent() has been removed
+To avoid the situation just described, the IDE classpath generation now more closely reflects the classpath as it is used by Gradle:
 
-This method (and all overloads) has been removed in 2.4, after [being deprecated in Gradle 2.3](http://gradle.org/docs/2.3/release-notes#component-metadata-rule-enhancements)
-and superseded by the [`all()` method](javadoc/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.html#all\(org.gradle.api.Action\)).
+- All transitive external and project dependencies of a project are listed as direct dependencies in the project.
+    - Different versions of a library are resolved by Gradle conflict resolution.
+- All dependencies in projects are marked as `exported = false`.
 
-### Removal of package scoped methods of `LogLevel`
+### Dependency Substitution
 
-The package scoped methods of the [`LogLevel`](javadoc/org/gradle/api/logging/LogLevel.html) enumeration have been removed.
+Gradle 2.4 included some of the new dependency substitution interfaces and classes, but they were unannounced. Build scripts written to use these interfaces in 2.4 will likely not work in 2.5.
+Please take a look at the dependency substitution sample and [User Guide chapter](userguide/dependency_management.html#dependency_substitution_rules) to understand the changes that were made
+while getting dependency substitution ready for the Gradle 2.5 release.
 
 ## External contributions
 
 We would like to thank the following community members for making contributions to this release of Gradle.
 
-* [Adrian Kelly](https://github.com/adrianbk) - Support for Amazon Web Services S3 dependency repositories
-* [Victor Bronstein](https://github.com/victorbr)
-    - Convert NotationParser implementations to NotationConverter
-    - Only parse Maven settings once per project to determine local Maven repository location (GRADLE-3219)
-* [Vyacheslav Blinov](https://github.com/dant3) - Fix for `test.testLogging.showStandardStreams = false` (GRADLE-3218)
-* [Michal Bendowski](https://github.com/bendowski-google) - Documentation improvements
-* [Daniel Siwiec](https://github.com/danielsiwiec) - Documentation improvements
-* [Andreas Schmid](https://github.com/aaschmid) - Improved test coverage for facet type configuration in `GenerateEclipseWtpFacet`
-* [Roman Donchenko](https://github.com/SpecLad)
-    - Fix PatternSet so that all files are not excluded when Ant global excludes are cleared (GRADLE-3254)
-    - Improvements to `Spec` implementations
-* [Lóránt Pintér](https://github.com/lptr) - Allow setting wrapper version on command-line
-* [Andreas Schmid](https://github.com/aaschmid) - Retain defaults when using `EclipseWtpComponent.resource()` and `EclipseWtpComponent.property()`
-* [Mikolaj Izdebski](https://github.com/mizdebsk) - Use hostname command as fallback way of getting build host name in Gradle build
-* [Andrea Cisternino](https://github.com/acisternino) - Make JavaFX available to Groovy compilation on Java 8
-* [Will Erickson](https://github.com/Sarev0k) - Support for annotation processing of Groovy code
-* [Noam Y. Tenne](https://github.com/noamt) - Declare a dependency on a specific timestamped Maven snapshot
-* [Thomas Broyer](https://github.com/tbroyer) - Better defaults for Java compilation source path
-
-We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
+* [Daniel Lacasse](https://github.com/Shad0w1nk) - Support Google Test for testing C++ binaries
+* [Lóránt Pintér](https://github.com/lptr), [Daniel Vigovszky](https://github.com/vigoo) and [Mark Vujevits](https://github.com/vujevits) - implement dependency substitution for projects
+* [Larry North](https://github.com/LarryNorth) - Build improvements.
+* [Tobias Schulte](https://github.com/tschulte) - Documentation improvements.
+* [Stefan Oehme](https://github.com/oehme) - Addition of `Project.copy(Action)` and `Project.copySpec(Action)`.
+* [Mikolaj Izdebski](https://github.com/mizdebsk) - Upgrade of the Maven publishing mechanisms to use Aether libraries.
+* [Lorin Hochstein](https://github.com/lorin) - Improvements to the ANTLR plugin documentation.
+* [Amit Portnoy](https://github.com/amitport) - Minor fix in LifecycleBasePlugin
 
 ## Known issues
 
diff --git a/subprojects/docs/src/docs/userguide/antlrPlugin.xml b/subprojects/docs/src/docs/userguide/antlrPlugin.xml
index cdf9d2a..28554ae 100644
--- a/subprojects/docs/src/docs/userguide/antlrPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/antlrPlugin.xml
@@ -169,7 +169,7 @@
                     Not null
                 </td>
                 <td>
-                    The ANTLR grammar files of this source set. Contains all <filename>.g</filename> files found in the ANTLR
+                    The ANTLR grammar files of this source set. Contains all <filename>.g</filename> or <filename>.g4</filename> files found in the ANTLR
                     source directories, and excludes all other types of files.
                 </td>
             </tr>
@@ -196,9 +196,11 @@
             This allows fine grained control over memory settings for the ANTLR process.
             To set the heap size of a ANTLR process, the <literal>maxHeapSize</literal> property
             of <apilink class="org.gradle.api.plugins.antlr.AntlrTask"/> can be used.
+            To pass additional command-line arguments, append to the <literal>arguments</literal> property
+            of <apilink class="org.gradle.api.plugins.antlr.AntlrTask"/>.
         </para>
-        <sample id="advanced" dir="antlr" title="setting custom max heap size for ANTLR">
-            <sourcefile file="build.gradle" snippet="memory-settings"/>
+        <sample id="advanced" dir="antlr" title="setting custom max heap size and extra arguments for ANTLR">
+            <sourcefile file="build.gradle" snippet="generate-grammar-settings"/>
         </sample>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/commandLine.xml b/subprojects/docs/src/docs/userguide/commandLine.xml
index 85737f3..7a6e4aa 100644
--- a/subprojects/docs/src/docs/userguide/commandLine.xml
+++ b/subprojects/docs/src/docs/userguide/commandLine.xml
@@ -184,18 +184,6 @@
         </varlistentry>
         <varlistentry>
             <term>
-                <option>--parallel-threads (deprecated, incubating)</option>
-            </term>
-            <listitem>
-                <para>
-                    Build projects in parallel, using the specified number of executor threads. For example<literal>--parallel-threads=3</literal>.
-                    This option should only be used with decoupled projects (see <xref linkend="sec:decoupled_projects"/>).
-                    This option has been replaced by <literal>--max-workers</literal>.
-                </para>
-            </listitem>
-        </varlistentry>
-        <varlistentry>
-            <term>
                 <option>--max-workers (incubating)</option>
             </term>
             <listitem>
@@ -279,6 +267,14 @@
             </listitem>
         </varlistentry>
         <varlistentry>
+            <term>
+                <option>-t</option>, <option>--continuous (incubating)</option>
+            </term>
+            <listitem>
+                <para>Enables <link linkend="continuous_build">continuous building</link> - Gradle will automatically re-run when changes are detected.</para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
             <term><option>-u</option>, <option>--no-search-upwards</option>
             </term>
             <listitem>
@@ -319,6 +315,19 @@
                     <para>Do not use color in the console output. This option has been replaced by the <option>--console plain</option> option.</para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>
+                    <option>--parallel-threads</option>
+                </term>
+                <listitem>
+                    <para>
+                        Build projects in parallel, using the specified number of executor threads.
+                        For example<literal>--parallel-threads=3</literal>.
+                        This option should only be used with decoupled projects (see <xref linkend="sec:decoupled_projects"/>).
+                        This option has been replaced by <literal>--max-workers</literal>.
+                    </para>
+                </listitem>
+            </varlistentry>
         </variablelist>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/continuousBuild.xml b/subprojects/docs/src/docs/userguide/continuousBuild.xml
new file mode 100644
index 0000000..d032c19
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/continuousBuild.xml
@@ -0,0 +1,162 @@
+<!--
+  ~ Copyright 2015 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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='continuous_build'>
+    <note>
+        <para>
+            Continuous build 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>
+    <title>Continuous build</title>
+    <para>
+        Typically, you ask Gradle to perform a single build by way of specifying tasks that Gradle should execute.
+        Gradle will determine the the actual set of tasks that need to be executed to satisfy the request, execute them all, and then stop doing work until the next request.
+        A continuous build differs in that Gradle will keep satisfying the initial build request (until instructed to stop)
+        by executing the build when it is detected that the result of the previous build is now out of date.
+        For example, if your build compiles Java source files to Java class files, a continuous build would automatically initiate a compile when the source files change.
+        Continuous build is useful for many scenarios.
+    </para>
+    <section id="continuous_build_start_stop">
+        <title>How do I start and stop a continuous build?</title>
+        <para>
+            A continuous build can be started by supplying either the <userinput>--continuous</userinput> or <userinput>-t</userinput> switches to Gradle,
+            along with the list of tasks, switches and arguments that define the work to do.
+            For example, <userinput>gradle build --continuous</userinput>.
+            This will have the same effect as running <userinput>gradle build</userinput>, but instead of Gradle exiting when done, it will wait for changes to the build inputs.
+            When a change occurs, <userinput>gradle build</userinput> will be automatically executed again and the process repeats.
+        </para>
+        <para>
+            If Gradle is attached to an interactive input source, such as a terminal, the continuous build can be exited by pressing <userinput>CTRL-D</userinput> (On Microsoft Windows, it is required to also press <userinput>ENTER</userinput> or <userinput>RETURN</userinput> after <userinput>CTRL-D</userinput>).
+            If Gradle is not attached to an interactive input source (e.g. is running as part of a script), the build process must be terminated (e.g. using the <userinput>kill</userinput> command or similar).
+            If the build is being executed via the Tooling API, the build can be cancelled using the Tooling API's cancellation mechanism.
+        </para>
+    </section>
+    <section id="continuous_build_causes">
+        <title>What will cause a subsequent build?</title>
+        <tip>
+            <title>Task file inputs</title>
+            <para>
+                Task implementations declare their file system inputs by annotating their properties with
+                <apilink class="org.gradle.api.tasks.InputFiles" /> and other similar annotations.
+                Please see <xref linkend="incrementalTask"/> for more information.
+            </para>
+        </tip>
+        <para>
+            At this time, <emphasis>only</emphasis> changes to input files to the previously executed tasks will initiate a build.
+            Furthermore, only changes that occur after the build has completed (and Gradle has prompted that it is waiting for changes) are noticed.
+            No other changes will initiate a build.
+            For example, changes to build scripts and build logic will not initiate build.
+            Likewise, changes to files that are read during the configuration of the build, not the execution, will not initiate a build.
+            In order to incorporate such changes, the continuous build must be restarted manually.
+        </para>
+        <para>
+            Consider a typical build using the <link linkend="java_plugin">Java plugin</link>, using the conventional filesystem layout.
+            The following diagram visualizes the task graph for <userinput>gradle build</userinput>:
+        </para>
+        <figure>
+            <title>Java plugin task graph</title>
+            <imageobject>
+                <imagedata fileref="img/javaPluginTasks.png"/>
+            </imageobject>
+        </figure>
+        <para>
+            The following key tasks of the graph use files in the corresponding directories as inputs:
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>compileJava</term>
+                <listitem><filename>src/main/java</filename></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>processResources</term>
+                <listitem><filename>src/main/resources</filename></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>compileTestJava</term>
+                <listitem><filename>src/test/java</filename></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>processTestResources</term>
+                <listitem><filename>src/test/resources</filename></listitem>
+            </varlistentry>
+        </variablelist>
+        <para>
+            Assuming that the initial build is successful (i.e. the <literal>build</literal> task and its dependencies complete without error),
+            changes to files in, or the addition/remove of files from, the locations listed above will initiate a new build.
+            If a change is made to a Java source file in <filename>src/main/java</filename>, the build will fire and all tasks will be scheduled.
+            Gradle's incremental build support ensures that only the tasks that are actually affected by the change are executed.
+        </para>
+        <para>
+            If the change to the main Java source causes compilation to fail, subsequent changes to the test source in <filename>src/test/java</filename> will not initiate a new build.
+            As the test source depends on the main source, there is no point building until the main source has changed, potentially fixing the compilation error.
+            After each build, only the inputs of the tasks that actually executed will be monitored for changes.
+        </para>
+        <para>
+            Continuous build is in no way coupled to compilation.
+            It works for all types of tasks.
+            For example, the <literal>processResources</literal> task copies and processes the files from <filename>src/main/resources</filename> for inclusion in the built JAR.
+            As such, a change to any file in this directory will also initiate a build.
+        </para>
+    </section>
+    <section id="continuous_build_limitations">
+        <title>Limitations and quirks</title>
+        <para>
+            There are several issues to be aware with the current implementation of continuous build.
+            These are likely to be addressed in future Gradle releases.
+        </para>
+        <section id="continuous_build_limitations_jdk">
+            <title>Requires Java 7 or later</title>
+            <para>
+                Gradle uses the JDK's <ulink url="http://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchService.html"><literal>WatchService</literal></ulink>
+                to receive notification of changes to files between builds.
+                This API was introduced in Java 7.
+                As such, Gradle's continuous build is not currently supported when building with Java 6.
+            </para>
+        </section>
+        <section>
+            <title>Performance and stability on Mac OS X</title>
+            <para>
+                The JDK file watching facility relies on inefficient file system polling on Mac OS X (see: <ulink url="https://bugs.openjdk.java.net/browse/JDK-7133447">JDK-7133447</ulink>).
+                This can significantly delay notification of changes on large projects with many source files.
+            </para>
+            <para>
+                Additionally, the watching mechanism may deadlock under <emphasis>heavy</emphasis> load on Mac OS X (see: <ulink url="https://bugs.openjdk.java.net/browse/JDK-8079620">JDK-8079620</ulink>).
+                This will manifest as Gradle appearing not to notice file changes.
+                If you suspect this is occurring, exit continuous build and start again.
+            </para>
+        </section>
+        <section>
+            <title>Changes to symbolic links</title>
+            <para>
+                <itemizedlist>
+                    <listitem><para>Creating or removing symbolic link to files will initiate a build.</para></listitem>
+                    <listitem><para>Modifying the target of a symbolic link will not cause a rebuild.</para></listitem>
+                    <listitem><para>Creating or removing symbolic links to directories will not cause rebuilds.</para></listitem>
+                    <listitem><para>Creating new files in the target directory of a symbolic link will not cause a rebuild.</para></listitem>
+                    <listitem><para>Deleting the target directory will not cause a rebuild.</para></listitem>
+                </itemizedlist>
+            </para>
+        </section>
+        <section>
+            <title>Changes to build logic are not considered</title>
+            <para>
+                The current implementation does not recalculate the build model on subsequent builds.
+                This means that changes to task configuration, or any other change to the build model, are effectively ignored.
+            </para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/customTasks.xml b/subprojects/docs/src/docs/userguide/customTasks.xml
index 5cb8786..58acfe7 100644
--- a/subprojects/docs/src/docs/userguide/customTasks.xml
+++ b/subprojects/docs/src/docs/userguide/customTasks.xml
@@ -196,6 +196,11 @@
                 <sourcefile file="build.gradle" snippet="incremental-task" />
             </sample>
             <para>
+                If for some reason the task is not run incremental, e.g. by running with --rerun-tasks, only the
+                outOfDate action is executed, even if there where deleted input files. You should consider handling this
+                case at the beginning, as is done in the example above.
+            </para>
+            <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>
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index aaffa5e..289ed80 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -682,6 +682,155 @@
             </tr>
         </table>
 
+        <section id='sub:maven_central'>
+            <title>Maven central repository</title>
+            <para>To add the central Maven 2 repository (<ulink url='http://repo1.maven.org/maven2'/>) simply add this to your build script:
+            </para>
+            <sample id="mavenCentral" dir="userguide/artifacts/defineRepository" title="Adding central Maven repository">
+                <sourcefile file="build.gradle" snippet="maven-central"/>
+            </sample>
+            <para>Now Gradle will look for your dependencies in this repository.</para>
+            <para>
+                <emphasis>Warning:</emphasis> Be aware that the central Maven 2 repository is HTTP only and
+                HTTPS is not supported. If you need a public HTTPS enabled central repository, you can use the <ulink url='http://jcenter.bintray.com'>JCenter</ulink> public repository (see <xref linkend="sub:maven_jcenter"/>).
+            </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='https://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. <emphasis>jcenter()</emphasis> uses HTTPS to connect to the repository.
+                If you want to use HTTP you can configure <literal>jcenter()</literal>:
+            </para>
+            <sample id="mavenJcenter" dir="userguide/artifacts/defineRepository" title="Using Bintrays's JCenter with HTTP">
+                <sourcefile file="build.gradle" snippet="maven-jcenter-http"/>
+            </sample>
+        </section>
+
+        <section id='sub:maven_local'>
+            <title>Local Maven repository</title>
+            <para>To use the local Maven cache as a repository you can do:</para>
+            <sample id="mavenLocalRepo" dir="userguide/artifacts/defineRepository" title="Adding the local Maven cache as a repository">
+                <sourcefile file="build.gradle" snippet="maven-local"/>
+            </sample>
+            <para>Gradle uses the same logic as Maven to identify the location of your local Maven cache. If a local repository location is defined in a <filename>settings.xml</filename>, this location
+                will be used. The <filename>settings.xml</filename> in <filename><replaceable>USER_HOME</replaceable>/.m2</filename> takes precedence over the <filename>settings.xml</filename>
+                in <filename><replaceable>M2_HOME</replaceable>/conf</filename>. If no <filename>settings.xml</filename> is available, Gradle uses the default location
+                <filename><replaceable>USER_HOME</replaceable>/.m2/repository</filename>.
+            </para>
+        </section>
+
+        <section id='sub:maven_repo'>
+            <title>Maven repositories</title>
+            <para>For adding a custom Maven repository you can do:
+            </para>
+            <sample id="mavenLikeRepo" dir="userguide/artifacts/defineRepository" title="Adding custom Maven repository">
+                <sourcefile file="build.gradle" snippet="maven-like-repo"/>
+            </sample>
+
+            <para>Sometimes a repository will have the POMs published to one location, and the JARs and other artifacts published at another location.
+                To define such a repository, you can do:
+            </para>
+            <sample id="mavenLikeRepoWithJarRepo" dir="userguide/artifacts/defineRepository" title="Adding additional Maven repositories for JAR files">
+                <sourcefile file="build.gradle" snippet="maven-like-repo-with-jar-repo"/>
+            </sample>
+            <para>Gradle will look at the first URL for the POM and the JAR. If the JAR can't be found there, the artifact URLs are used to look for JARs.
+            </para>
+            <section>
+                <title>Accessing password protected Maven repositories</title>
+                <para>To access a Maven repository which uses basic authentication, you specify the username and password to use when you define the repository:
+                </para>
+                <sample id="mavenPasswordProtectedRepo" dir="userguide/artifacts/defineRepository" title="Accessing password protected Maven repository">
+                    <sourcefile file="build.gradle" snippet="authenticated-maven-repo"/>
+                </sample>
+                <para>It is advisable to keep your username and password in <filename>gradle.properties</filename> rather than directly in the build file.
+                </para>
+            </section>
+        </section>
+
+        <section id='sec:flat_dir_resolver'>
+            <title>Flat directory repository</title>
+            <para>If you want to use a (flat) filesystem directory as a repository, simply type:
+            </para>
+            <sample id="flatDirMulti" dir="userguide/artifacts/defineRepository" title="Flat repository resolver">
+                <sourcefile file="build.gradle" snippet="flat-dir-multi"/>
+            </sample>
+            <para>This adds repositories which look into one or more directories for finding dependencies. Note that
+                this type of repository does not support any meta-data formats like Ivy XML or Maven POM files. Instead,
+                Gradle will dynamically generate a module descriptor (without any dependency information) based on the
+                presence of artifacts. However, as Gradle prefers to use modules whose descriptor has been created from
+                real meta-data rather than being generated, flat directory repositories cannot be used to override
+                artifacts with real meta-data from other repositories. So, for example, if Gradle finds only
+                <filename>jmxri-1.2.1.jar</filename> in a flat directory repository, but <filename>jmxri-1.2.1.pom</filename>
+                in another repository that supports meta-data, it will use the second repository to provide the module.
+                For the use case of overriding remote artifacts with local ones consider using an Ivy or Maven repository
+                instead whose URL points to a local directory.
+                If you only work with flat directory repositories you don't need to set all attributes of a dependency.
+                See <xref linkend='para:dependencies_with_empty_attributes'/>.
+            </para>
+        </section>
+
+        <section id="sec:ivy_repositories">
+            <title>Ivy repositories</title>
+            <section>
+                <title>Defining an Ivy repository with a standard layout</title>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
+                    <sourcefile file="build.gradle" snippet="ivy-repo"/>
+                </sample>
+            </section>
+            <section>
+                <title>Defining a named layout for an Ivy repository</title>
+                <para>
+                    You can specify that your repository conforms to the Ivy or Maven default layout by using a named layout.
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with named layout">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-maven-layout"/>
+                </sample>
+                <para>
+                    Valid named layout values are <literal>'gradle'</literal> (the default), <literal>'maven'</literal> and <literal>'ivy'</literal>.
+                    See <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository" method="layout(java.lang.String, groovy.lang.Closure)"/> in the API documentation for details of these named layouts.
+                </para>
+            </section>
+            <section>
+                <title>Defining custom pattern layout for an Ivy repository</title>
+                <para>To define an Ivy repository with a non-standard layout, you can define a 'pattern' layout for the repository:
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with pattern layout">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-pattern-layout"/>
+                </sample>
+                <para>To define an Ivy repository which fetches Ivy files and artifacts from different locations,
+                    you can define separate patterns to use to locate the Ivy files and artifacts:
+                </para>
+                <para>
+                    Each <literal>artifact</literal> or <literal>ivy</literal> specified for a repository adds an <emphasis>additional</emphasis> pattern to use.
+                    The patterns are used in the order that they are defined.
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with multiple custom patterns">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-custom-pattern"/>
+                </sample>
+                <para>Optionally, a repository with pattern layout can have its 'organisation' part laid out in Maven style, with
+                    forward slashes replacing dots as separators. For example, the organisation <literal>my.company</literal> would then be represented as <literal>my/company</literal>.
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with Maven compatible layout">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-m2compatible-layout"/>
+                </sample>
+            </section>
+            <section>
+                <title>Accessing password protected Ivy repositories</title>
+                <para>To access an Ivy repository which uses basic authentication, you specify the username and password to use when you define the repository:
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
+                    <sourcefile file="build.gradle" snippet="authenticated-ivy-repo"/>
+                </sample>
+            </section>
+        </section>
+
         <section id='sub:supported_transport_protocols'>
             <title>Supported repository transport protocols</title>
             <para>Maven and Ivy repositories support the use of various transport protocols. At the moment the following protocols are supported:
@@ -835,155 +984,6 @@
             </section>
         </section>
 
-        <section id='sub:maven_central'>
-            <title>Maven central repository</title>
-            <para>To add the central Maven 2 repository (<ulink url='http://repo1.maven.org/maven2'/>) simply add this to your build script:
-            </para>
-            <sample id="mavenCentral" dir="userguide/artifacts/defineRepository" title="Adding central Maven repository">
-                <sourcefile file="build.gradle" snippet="maven-central"/>
-            </sample>
-            <para>Now Gradle will look for your dependencies in this repository.</para>
-            <para>
-                <emphasis>Warning:</emphasis> Be aware that the central Maven 2 repository is HTTP only and
-                HTTPS is not supported. If you need a public HTTPS enabled central repository, you can use the <ulink url='http://jcenter.bintray.com'>JCenter</ulink> public repository (see <xref linkend="sub:maven_jcenter"/>).
-            </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='https://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. <emphasis>jcenter()</emphasis> uses HTTPS to connect to the repository.
-                If you want to use HTTP you can configure <literal>jcenter()</literal>:
-            </para>
-            <sample id="mavenJcenter" dir="userguide/artifacts/defineRepository" title="Using Bintrays's JCenter with HTTP">
-                <sourcefile file="build.gradle" snippet="maven-jcenter-http"/>
-            </sample>
-        </section>
-
-        <section id='sub:maven_local'>
-            <title>Local Maven repository</title>
-            <para>To use the local Maven cache as a repository you can do:</para>
-            <sample id="mavenLocalRepo" dir="userguide/artifacts/defineRepository" title="Adding the local Maven cache as a repository">
-                <sourcefile file="build.gradle" snippet="maven-local"/>
-            </sample>
-            <para>Gradle uses the same logic as Maven to identify the location of your local Maven cache. If a local repository location is defined in a <filename>settings.xml</filename>, this location
-                will be used. The <filename>settings.xml</filename> in <filename><replaceable>USER_HOME</replaceable>/.m2</filename> takes precedence over the <filename>settings.xml</filename>
-                in <filename><replaceable>M2_HOME</replaceable>/conf</filename>. If no <filename>settings.xml</filename> is available, Gradle uses the default location
-                <filename><replaceable>USER_HOME</replaceable>/.m2/repository</filename>.
-            </para>
-        </section>
-
-        <section id='sub:maven_repo'>
-            <title>Maven repositories</title>
-            <para>For adding a custom Maven repository you can do:
-            </para>
-            <sample id="mavenLikeRepo" dir="userguide/artifacts/defineRepository" title="Adding custom Maven repository">
-                <sourcefile file="build.gradle" snippet="maven-like-repo"/>
-            </sample>
-
-            <para>Sometimes a repository will have the POMs published to one location, and the JARs and other artifacts published at another location.
-                To define such a repository, you can do:
-            </para>
-            <sample id="mavenLikeRepoWithJarRepo" dir="userguide/artifacts/defineRepository" title="Adding additional Maven repositories for JAR files">
-                <sourcefile file="build.gradle" snippet="maven-like-repo-with-jar-repo"/>
-            </sample>
-            <para>Gradle will look at the first URL for the POM and the JAR. If the JAR can't be found there, the artifact URLs are used to look for JARs.
-            </para>
-            <section>
-                <title>Accessing password protected Maven repositories</title>
-                <para>To access a Maven repository which uses basic authentication, you specify the username and password to use when you define the repository:
-                </para>
-                <sample id="mavenPasswordProtectedRepo" dir="userguide/artifacts/defineRepository" title="Accessing password protected Maven repository">
-                    <sourcefile file="build.gradle" snippet="authenticated-maven-repo"/>
-                </sample>
-                <para>It is advisable to keep your username and password in <filename>gradle.properties</filename> rather than directly in the build file.
-                </para>
-            </section>
-        </section>
-
-        <section id='sec:flat_dir_resolver'>
-            <title>Flat directory repository</title>
-            <para>If you want to use a (flat) filesystem directory as a repository, simply type:
-            </para>
-            <sample id="flatDirMulti" dir="userguide/artifacts/defineRepository" title="Flat repository resolver">
-                <sourcefile file="build.gradle" snippet="flat-dir-multi"/>
-            </sample>
-            <para>This adds repositories which look into one or more directories for finding dependencies. Note that
-                this type of repository does not support any meta-data formats like Ivy XML or Maven POM files. Instead,
-                Gradle will dynamically generate a module descriptor (without any dependency information) based on the
-                presence of artifacts. However, as Gradle prefers to use modules whose descriptor has been created from
-                real meta-data rather than being generated, flat directory repositories cannot be used to override
-                artifacts with real meta-data from other repositories. So, for example, if Gradle finds only
-                <filename>jmxri-1.2.1.jar</filename> in a flat directory repository, but <filename>jmxri-1.2.1.pom</filename>
-                in another repository that supports meta-data, it will use the second repository to provide the module.
-                For the use case of overriding remote artifacts with local ones consider using an Ivy or Maven repository
-                instead whose URL points to a local directory.
-                If you only work with flat directory repositories you don't need to set all attributes of a dependency.
-                See <xref linkend='para:dependencies_with_empty_attributes'/>.
-            </para>
-        </section>
-
-        <section id="sec:ivy_repositories">
-            <title>Ivy repositories</title>
-            <section>
-                <title>Defining an Ivy repository with a standard layout</title>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
-                    <sourcefile file="build.gradle" snippet="ivy-repo"/>
-                </sample>
-            </section>
-            <section>
-                <title>Defining a named layout for an Ivy repository</title>
-                <para>
-                    You can specify that your repository conforms to the Ivy or Maven default layout by using a named layout.
-                </para>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with named layout">
-                    <sourcefile file="build.gradle" snippet="ivy-repo-with-maven-layout"/>
-                </sample>
-                <para>
-                    Valid named layout values are <literal>'gradle'</literal> (the default), <literal>'maven'</literal> and <literal>'ivy'</literal>.
-                    See <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository" method="layout(java.lang.String, groovy.lang.Closure)"/> in the API documentation for details of these named layouts.
-                </para>
-            </section>
-            <section>
-                <title>Defining custom pattern layout for an Ivy repository</title>
-                <para>To define an Ivy repository with a non-standard layout, you can define a 'pattern' layout for the repository:
-                </para>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with pattern layout">
-                    <sourcefile file="build.gradle" snippet="ivy-repo-with-pattern-layout"/>
-                </sample>
-                <para>To define an Ivy repository which fetches Ivy files and artifacts from different locations,
-                    you can define separate patterns to use to locate the Ivy files and artifacts:
-                </para>
-                <para>
-                    Each <literal>artifact</literal> or <literal>ivy</literal> specified for a repository adds an <emphasis>additional</emphasis> pattern to use.
-                    The patterns are used in the order that they are defined.
-                </para>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with multiple custom patterns">
-                    <sourcefile file="build.gradle" snippet="ivy-repo-with-custom-pattern"/>
-                </sample>
-                <para>Optionally, a repository with pattern layout can have its 'organisation' part laid out in Maven style, with
-                    forward slashes replacing dots as separators. For example, the organisation <literal>my.company</literal> would then be represented as <literal>my/company</literal>.
-                </para>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with Maven compatible layout">
-                    <sourcefile file="build.gradle" snippet="ivy-repo-with-m2compatible-layout"/>
-                </sample>
-            </section>
-            <section>
-                <title>Accessing password protected Ivy repositories</title>
-                <para>To access an Ivy repository which uses basic authentication, you specify the username and password to use when you define the repository:
-                </para>
-                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
-                    <sourcefile file="build.gradle" snippet="authenticated-ivy-repo"/>
-                </sample>
-            </section>
-        </section>
-
         <section>
             <title>Working with repositories</title>
             <para>To access a repository:</para>
@@ -1182,56 +1182,104 @@ someroot/[artifact]-[revision].[ext]
                     </sample>
                 </para>
             </section>
-            <section id='sec:module_replacement'>
-                <title>Declaring that a legacy library is replaced by a new one</title>
+        </section>
+        <section id="dependency_substitution_rules">
+            <title>Dependency Substitution Rules</title>
+            <para>
+                Dependency substitution rules work similarly to dependency resolve rules.  In fact, many capabilities of dependency resolve rules
+                can be implemented with dependency substitution rules.  They allow project and module dependencies to be transparently substituted with
+                specified replacements.
+                Unlike dependency resolve rules, dependency substitution rules allow project and module dependencies to be substituted interchangeably.
+            </para>
+            <note>
                 <para>
-                    A good example when a new library replaced a legacy one is the "google-collections" -> "guava" migration.
-                    The team that created google-collections decided to change the module name from "com.google.collections:google-collections" into "com.google.guava:guava".
-                    This a legal scenario in the industry: teams need to be able to change the names of products they maintain, including the module coordinates.
-                    Renaming of the module coordinates has impact on conflict resolution.
+                    <emphasis>NOTE: Adding a dependency substitution rule to a configuration changes the timing of when that configuration is resolved.</emphasis>
+                    Instead of being resolved on first use, the configuration is instead resolved when the task graph is being constructed. This can have unexpected
+                    consequences if the configuration is being further modified during task execution, or if the configuration relies on modules that are published during
+                    execution of another task.
                 </para>
+
                 <para>
-                    To explain the impact on conflict resolution, let's consider the "google-collections" -> "guava" scenario.
-                    It may happen that both libraries are pulled into the same dependency graph.
-                    For example, "our" project depends on guava but some of our dependencies pull in a legacy version of google-collections.
-                    This can cause runtime errors, for example during test or application execution.
-                    Gradle does not automatically resolve the google-collections VS guava conflict because it is not considered as a "version conflict".
-                    It's because the module coordinates for both libraries are completely different and conflict resolution is activated when
-                    "group" and "name" coordinates are the same but there are different versions available in the dependency graph
-                    (for more info, please refer to the section on conflict resolution).
-                    Traditional remedies to this problem are:
+                    To explain:
                     <itemizedlist>
-                        <listitem>Declare exclusion rule to avoid pulling in "google-collections" to graph. It is probably the most popular approach.</listitem>
-                        <listitem>Avoid dependencies that pull in legacy libraries.</listitem>
-                        <listitem>Upgrade the dependency version if the new version no longer pulls in a legacy library.</listitem>
-                        <listitem>Downgrade to "google-collections". It's not recommended, just mentioned for completeness.</listitem>
+                        <listitem>A <literal>Configuration</literal> can be declared as an input to any Task, and that configuration can include project dependencies when it is resolved.</listitem>
+                        <listitem>If a project dependency is an input to a Task (via a configuration), then tasks to built the project artifacts must be added to the task dependencies.</listitem>
+                        <listitem>In order to determine the project dependencies that are inputs to a task, Gradle needs to resolve the <literal>Configuration</literal> inputs.</listitem>
+                        <listitem>Because the Gradle task graph is fixed once task execution has commenced, Gradle needs to perform this resolution prior to executing any tasks.</listitem>
                     </itemizedlist>
-                    Traditional approaches work but they are not general enough.
-                    For example, an organisation wants to resolve the google-collections VS guava conflict resolution problem in all projects.
-                    Starting from Gradle 2.2 it is possible to declare that certain module was replaced by other.
-                    This enables organisations to include the information about module replacement in the corporate plugin suite and resolve the problem
-                    holistically for all Gradle-powered projects in the enterprise.
-                    <sample id="module_replacement_declaration" dir="userguide/artifacts/componentModuleMetadata" title="Declaring module replacement">
-                        <sourcefile file="build.gradle" snippet="module_replacement_declaration"/>
-                    </sample>
-                    For more examples and detailed API, please refer to the DSL reference for <apilink class="org.gradle.api.artifacts.dsl.ComponentMetadataHandler"/>.
                 </para>
                 <para>
-                    What happens when we declare that "google-collections" are replaced by "guava"?
-                    Gradle can use this information for conflict resolution.
-                    Gradle will consider every version of "guava" newer/better than any version of "google-collections".
-                    Also, Gradle will ensure that only guava jar is present in the classpath / resolved file list.
-                    Please note that if only "google-collections" appears in the dependency graph (e.g. no "guava")
-                    Gradle will not eagerly replace it with "guava".
-                    Module replacement is an information that Gradle uses for resolving conflicts. If there is no conflict
-                    (e.g. only "google-collections" or only "guava" in the graph) the replacement information is not used.
+                    In the absence of dependency substitution rules, Gradle knows that an external module dependency will never transitively reference a project dependency.
+                    This makes it easy to determine the full set of project dependencies for a configuration through simple graph traversal.
+                    With this functionality, Gradle can no longer make this assumption, and must perform a full resolve in order to determine the project dependencies.
+                </para>
+            </note>
+            <section id='sec:module_to_project_substitution'>
+                <title>Substituting an external module dependency with a project dependency</title>
+                <para>
+                    One use case for dependency substitution is to use a locally developed version of a module in place of one that is downloaded from
+                    an external repository. This could be useful for testing a local, patched version of a dependency.
                 </para>
                 <para>
-                    Currently it is not possible to declare that certain modules is replaced by a set of modules.
-                    However, it is possible to declare that multiple modules are replaced by a single module.
+                    The module to be replaced can be declared with or without a version specified.
+                </para>
+                <sample id="module_to_project_substitution" dir="userguide/artifacts/dependency-substitution" title="Substituting a module with a project">
+                    <sourcefile file="build.gradle" snippet="module_to_project_substitution"/>
+                </sample>
+                <para>
+                    Note that a project that is substituted must be included in the multi-project build (via settings.gradle).  Dependency substitution
+                    rules take care of replacing the module dependency with the project dependency and wiring up any task dependencies,
+                    but do not implicitly include the project in the build.
+                </para>
+            </section>
+            <section id='sec:project_to_module_substitution'>
+                <title>Substituting a project dependency with a module replacement</title>
+                <para>
+                    Another way to use substitution rules is to replace a project dependency with a module in a multi-project build.
+                    This can be useful to speed up development with a large multi-project build, by allowing a subset of the project
+                    dependencies to be downloaded from a repository rather than being built.
+                </para>
+                <para>
+                    The module to be used as a replacement must be declared with a version specified.
+                </para>
+                <sample id="project_to_module_substitution" dir="userguide/artifacts/dependency-substitution" title="Substituting a project with a module">
+                    <sourcefile file="build.gradle" snippet="project_to_module_substitution"/>
+                </sample>
+                <para>
+                    When a project dependency has been replaced with a module dependency, that project is still included in the overall multi-project build.
+                    However, tasks to build the replaced dependency will not be executed in order to build the resolve the depending <literal>Configuration</literal>.
+                </para>
+            </section>
+            <section id='sec:conditional_dependency_substitution'>
+                <title>Conditionally substituting a dependency</title>
+                <para>
+                    A common use case for dependency substitution is to allow more flexible assembly of sub-projects within a multi-project build.
+                    This can be useful for developing a local, patched version of an external dependency or for building a subset of the modules within
+                    a large multi-project build.
+                </para>
+                <para>
+                    The following example uses a dependency substitution rule to replace any module dependency with the group "org.example", but
+                    only if a local project matching the dependency name can be located.
+                </para>
+                <sample id="project_substitution" dir="dependency-substitution" title="Conditionally substituting a dependency">
+                    <sourcefile file="build.gradle" snippet="project_substitution"/>
+                </sample>
+                <para>
+                    Note that a project that is substituted must be included in the multi-project build (via settings.gradle).  Dependency substitution
+                    rules take care of replacing the module dependency with the project dependency, but do not implicitly include the project in the build.
                 </para>
             </section>
-
+        </section>
+        <section id="sec:configuration_defaults">
+            <title>Specifying default dependencies for a configuration</title>
+            <para>
+                A configuration can be configured with default dependencies to be used if no dependencies are explicitly set for the configuration.
+                A primary use case of this functionality is for developing plugins that make use of versioned tools that the user might override.  By specifying
+                default dependencies, the plugin can use a default version of the tool only if the user has not specified a particular version to use.
+            </para>
+            <sample id="configuration_default_dependencies" dir="userguide/artifacts/defineConfiguration" title="Specifying default dependencies on a configuration">
+                <sourcefile file="build.gradle" snippet="configuration-default-dependencies"/>
+            </sample>
         </section>
         <section id="ivy_dynamic_resolve_mode">
             <title>Enabling Ivy dynamic resolve mode</title>
@@ -1373,6 +1421,56 @@ someroot/[artifact]-[revision].[ext]
                 <sourcefile file="build.gradle" snippet="api-component-selection" />
             </sample>
         </section>
+        <section id='sec:module_replacement'>
+            <title>Module replacement rules</title>
+            <para>
+                Module replacement rules allow a build to declare that a legacy library has been replaced by a new one.
+                A good example when a new library replaced a legacy one is the "google-collections" -> "guava" migration.
+                The team that created google-collections decided to change the module name from "com.google.collections:google-collections" into "com.google.guava:guava".
+                This a legal scenario in the industry: teams need to be able to change the names of products they maintain, including the module coordinates.
+                Renaming of the module coordinates has impact on conflict resolution.
+            </para>
+            <para>
+                To explain the impact on conflict resolution, let's consider the "google-collections" -> "guava" scenario.
+                It may happen that both libraries are pulled into the same dependency graph.
+                For example, "our" project depends on guava but some of our dependencies pull in a legacy version of google-collections.
+                This can cause runtime errors, for example during test or application execution.
+                Gradle does not automatically resolve the google-collections VS guava conflict because it is not considered as a "version conflict".
+                It's because the module coordinates for both libraries are completely different and conflict resolution is activated when
+                "group" and "name" coordinates are the same but there are different versions available in the dependency graph
+                (for more info, please refer to the section on conflict resolution).
+                Traditional remedies to this problem are:
+                <itemizedlist>
+                    <listitem>Declare exclusion rule to avoid pulling in "google-collections" to graph. It is probably the most popular approach.</listitem>
+                    <listitem>Avoid dependencies that pull in legacy libraries.</listitem>
+                    <listitem>Upgrade the dependency version if the new version no longer pulls in a legacy library.</listitem>
+                    <listitem>Downgrade to "google-collections". It's not recommended, just mentioned for completeness.</listitem>
+                </itemizedlist>
+                Traditional approaches work but they are not general enough.
+                For example, an organisation wants to resolve the google-collections VS guava conflict resolution problem in all projects.
+                Starting from Gradle 2.2 it is possible to declare that certain module was replaced by other.
+                This enables organisations to include the information about module replacement in the corporate plugin suite and resolve the problem
+                holistically for all Gradle-powered projects in the enterprise.
+                <sample id="module_replacement_declaration" dir="userguide/artifacts/componentModuleMetadata" title="Declaring module replacement">
+                    <sourcefile file="build.gradle" snippet="module_replacement_declaration"/>
+                </sample>
+                For more examples and detailed API, please refer to the DSL reference for <apilink class="org.gradle.api.artifacts.dsl.ComponentMetadataHandler"/>.
+            </para>
+            <para>
+                What happens when we declare that "google-collections" are replaced by "guava"?
+                Gradle can use this information for conflict resolution.
+                Gradle will consider every version of "guava" newer/better than any version of "google-collections".
+                Also, Gradle will ensure that only guava jar is present in the classpath / resolved file list.
+                Please note that if only "google-collections" appears in the dependency graph (e.g. no "guava")
+                Gradle will not eagerly replace it with "guava".
+                Module replacement is an information that Gradle uses for resolving conflicts. If there is no conflict
+                (e.g. only "google-collections" or only "guava" in the graph) the replacement information is not used.
+            </para>
+            <para>
+                Currently it is not possible to declare that certain modules is replaced by a set of modules.
+                However, it is possible to declare that multiple modules are replaced by a single module.
+            </para>
+        </section>
     </section>
     <section id='sec:dependency_cache'>
         <title>The dependency cache</title>
diff --git a/subprojects/docs/src/docs/userguide/distributionPlugin.xml b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
index e4484b5..b5dee42 100644
--- a/subprojects/docs/src/docs/userguide/distributionPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
@@ -94,7 +94,7 @@
                     <literal>assembleDist</literal>
                 </td>
                 <td>
-                    <literal>-</literal>
+                    <literal>distTar</literal>, <literal>distZip</literal>
                 </td>
                 <td>
                     <apilink class="org.gradle.api.Task"/>
@@ -162,7 +162,7 @@
                     <literal>assemble<replaceable>${distribution.name.capitalize()}</replaceable>Dist</literal>
                 </td>
                 <td>
-                    <literal>-</literal>
+                    <literal><replaceable>${distribution.name}</replaceable>DistTar</literal>, <literal><replaceable>${distribution.name}</replaceable>DistZip</literal>
                 </td>
                 <td>
                     <apilink class="org.gradle.api.Task"/>
diff --git a/subprojects/docs/src/docs/userguide/nativeBinaries.xml b/subprojects/docs/src/docs/userguide/nativeBinaries.xml
index a20fee0..897f3c6 100755
--- a/subprojects/docs/src/docs/userguide/nativeBinaries.xml
+++ b/subprojects/docs/src/docs/userguide/nativeBinaries.xml
@@ -486,6 +486,42 @@
         </section>
     </section>
 
+    <section id="native_binaries:preCompiledHeaders">
+        <title>Precompiled Headers</title>
+        <para>
+            Precompiled headers are a performance optimization that reduces the cost of compiling widely used headers multiple times.
+            This feature <firstterm>precompiles</firstterm> a header such that the compiled object file can be reused when
+            compiling each source file rather than recompiling the header each time.  This support is available for C, C++, Objective-C,
+            and Objective-C++ builds.
+        </para>
+        <para>
+            To configure a precompiled header, first a header file needs to be defined that includes all of the headers that should
+            be precompiled.  It must be specified as the first included header in every source file where the precompiled header
+            should be used.  It is assumed that this header file, and any headers it contains, make use of header guards so that they can be included
+            in an idempotent manner.  If header guards are not used in a header file, it is possible the header could be compiled more
+            than once and could potentially lead to a broken build.
+        </para>
+        <sample id="preCompiledHeaderFile" dir="native-binaries/pre-compiled-headers" title="Creating a precompiled header file">
+            <sourcefile file="src/hello/headers/pch.h" />
+        </sample>
+        <sample id="preCompiledHeaderFile" dir="native-binaries/pre-compiled-headers" title="Including a precompiled header file in a source file">
+            <sourcefile file="src/hello/cpp/hello.cpp" />
+        </sample>
+        <para>
+            Precompiled headers are specified on a source set.  Only one precompiled header file can be specified on a given source set and will
+            be applied to all source files that declare it as the first include.  If a source files does not include this header file as the
+            first header, the file will be compiled in the normal manner (without making use of the precompiled header object file).  The string provided
+            should be the same as that which is used in the "#include" directive in the source files.
+        </para>
+        <sample id="preCompiledHeaderConfig" dir="native-binaries/pre-compiled-headers" title="Configuring a precompiled header">
+            <sourcefile file="build.gradle" snippet="libraries"/>
+        </sample>
+        <para>
+            A precompiled header must be included in the same way for all files that use it.  Usually, this means the header file should exist
+            in the source set "headers" directory or in a directory included on the compiler include path.
+        </para>
+    </section>
+
     <section id="native_binaries:variants">
         <title>Native Binary Variants</title>
         <para>
@@ -738,7 +774,7 @@
             <title>CUnit sources</title>
             <para>
                 Gradle will create a <apilink class="org.gradle.language.c.CSourceSet"/> named 'cunit' for each <apilink class="org.gradle.nativeplatform.test.cunit.CUnitTestSuiteSpec"/> 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
+                in the project. This source set should contain the cunit test files for the component under test. 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>
@@ -817,7 +853,7 @@
             <title>GoogleTest sources</title>
             <para>
                 Gradle will create a <apilink class="org.gradle.language.cpp.CppSourceSet"/> named 'cpp' for each <apilink class="org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteSpec"/> component
-                in the project. This source set should contain the GoogleTest test files for the component sources. Source files can be located in the conventional location
+                in the project. This source set should contain the GoogleTest test files for the component under test. Source files can be located in the conventional location
                 (<literal>src/${component.name}Test/cpp</literal>) or can be configured like any other source set.
             </para>
         </section>
@@ -831,7 +867,7 @@
                 <apilink class="org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec"/> 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="googleTestSources" dir="native-binaries/google-test" title="Registering GoogleTest tests">
+            <sample id="googleTestSources" dir="native-binaries/google-test" title="Registering GoogleTest tests" includeLocation="true">
                 <sourcefile file="build.gradle" snippet="configure-test-binary"/>
             </sample>
             <note>
@@ -847,7 +883,7 @@
                 which will run all of the registered GoogleTest tests.
                 Test results will be found in the <literal><replaceable>${build.dir}</replaceable>/test-results</literal> directory.
             </para>
-            <!-- TODO:DAZ Add this back in when we have functional libs -->
+            <!-- Google test sample output is nondeterministic -->
             <!--<sample id="completeGoogleTestExample" dir="native-binaries/google-test" title="Running GoogleTest tests" includeLocation="true">-->
                 <!--<sourcefile file="build.gradle" snippet="complete-example"/>-->
                 <!--<output args='-q runFailingOperatorsTestGoogleTestExe' expectFailure="true"/>-->
diff --git a/subprojects/docs/src/docs/userguide/newModel.xml b/subprojects/docs/src/docs/userguide/newModel.xml
index 5434d59..1d28290 100644
--- a/subprojects/docs/src/docs/userguide/newModel.xml
+++ b/subprojects/docs/src/docs/userguide/newModel.xml
@@ -36,7 +36,7 @@
     <section>
         <title>Background</title>
         <para>
-            Gradle embraces domain modelling as core tenet.
+            Gradle embraces domain modelling as a core tenet.
             Focusing on the domain model as opposed to the execution model (like prior generation build tools such as Apache Ant) has many advantages.
             A strong domain model communicates the intent (i.e. the what) over the mechanics (i.e. the how).
             This allows humans to understand builds at a level that is meaningful to them.
@@ -69,7 +69,7 @@
             By defining build tasks as effectively a graph of dependent functions with explicit inputs and outputs,
             Gradle is able to order, cache, parallelize and apply other optimizations to the work.
             Using a “graph of tasks” for the production of software is a long established idea, and necessary given the complexity of software production.
-            The task graph effectively defines the <emphasis>rules</emphasis> of execution the Gradle must follow.
+            The task graph effectively defines the <emphasis>rules</emphasis> of execution that Gradle must follow.
             The term “Rule based model configuration” refers to applying the same concepts to building the model that builds the task graph.
         </para>
         <para>
@@ -195,7 +195,7 @@
                     Rules are bound within a “scope”, which determines how references bind.
                     Most rules are bound at the project scope (i.e. the root of the model graph for the project).
                     However, rules can be scoped to a node within the graph.
-                    The <apilink class="org.gradle.model.collection.CollectionBuilder" method="named(java.lang.String, java.lang.Class)" /> method is an example,
+                    The <apilink class="org.gradle.model.ModelMap" method="named(java.lang.String, java.lang.Class)" /> method is an example,
                     of a mechanism for applying scoped rules.
                     Rules declared in the build script using the <literal>model {}</literal> block, or via a <literal>RuleSource</literal> applied as a plugin use the root of the model space as the scope.
                     This can be considered the default scope.
@@ -239,7 +239,7 @@
             <sourcefile file='build.gradle' snippet='create-rule'/>
         </sample>
         <para>
-            This rule declares the there is a model element at path <literal>"person"</literal> (defined by the method name), of type <literal>Person</literal>.
+            This rule declares that there is a model element at path <literal>"person"</literal> (defined by the method name), of type <literal>Person</literal>.
             This is the form of the <apilink class="org.gradle.model.Model" /> type rule for <apilink class="org.gradle.model.Managed" /> types.
             Here, the person object is the rule subject.
             The method could potentially have a body, that mutated the person instance.
@@ -259,8 +259,8 @@
         </sample>
         <para>
             This <apilink class="org.gradle.model.Mutate" /> rule effectively adds a task, by mutating the tasks collection.
-            The subject here is the <literal>"tasks"</literal> node, which is available as a <apilink class="org.gradle.model.collection.CollectionBuilder" /> of <apilink class="org.gradle.api.Task" />.
-            The lone input is our person element.
+            The subject here is the <literal>"tasks"</literal> node, which is available as a <apilink class="org.gradle.model.ModelMap" /> of <apilink class="org.gradle.api.Task" />.
+            The only input is our person element.
             As the person is being used as an input here, it will have been realised before executing this rule.
             That is, the task container effectively <emphasis>depends on</emphasis> the person element.
             If there are other configuration rules for the person element, potentially specified in a build script or other plugin, the will also be guaranteed to have been executed.
@@ -367,4 +367,4 @@ model {
             Please be sure to consult the documentation of Gradle corresponding to the version you are using and to watch for changes announced in the release notes for future versions.
         </para>
     </section>
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/signingPlugin.xml b/subprojects/docs/src/docs/userguide/signingPlugin.xml
index 94ce070..dcd54f0 100644
--- a/subprojects/docs/src/docs/userguide/signingPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/signingPlugin.xml
@@ -23,8 +23,8 @@
         as well as other information such as when the signature was generated.
     </para>
     <para>
-        The signing plugin currently only provides support for generating <ulink url='http://www.pgpi.org/'>PGP signatures</ulink> 
-        (which is the signature format <ulink url="https://docs.sonatype.org/display/Repository/Central+Sync+Requirements">required for 
+        The signing plugin currently only provides support for generating <ulink url='http://www.pgpi.org/'>PGP signatures</ulink>
+        (which is the signature format <ulink url="http://central.sonatype.org/pages/requirements.html#sign-files-with-gpgpgp">required for
         publication to the Maven Central Repository</ulink>).
     </para>
 
@@ -35,13 +35,13 @@
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
     </section>
-    
+
     <section>
         <title>Signatory credentials</title>
         <para>
-            In order to create PGP signatures, you will need a key pair (instructions on creating a key pair using the <ulink url="http://www.gnupg.org/">GnuPG tools</ulink> 
+            In order to create PGP signatures, you will need a key pair (instructions on creating a key pair using the <ulink url="http://www.gnupg.org/">GnuPG tools</ulink>
             can be found in the <ulink url="http://www.gnupg.org/documentation/howtos.html">GnuPG HOWTOs</ulink>). You need to provide the signing plugin
-            with your key information, which means three things: 
+            with your key information, which means three things:
         </para>
         <itemizedlist>
             <listitem><para>The public key ID (an 8 character hexadecimal string).</para></listitem>
@@ -49,7 +49,7 @@
             <listitem><para>The passphrase used to protect your private key.</para></listitem>
         </itemizedlist>
         <para>
-            These items must be supplied as the values of properties <literal>signing.keyId</literal>,  
+            These items must be supplied as the values of properties <literal>signing.keyId</literal>,
             <literal>signing.secretKeyRingFile</literal>, and <literal>signing.password</literal> respectively. Given the personal and private nature of these values, a good practice
             is to store them in the user <literal>gradle.properties</literal> file (described in <xref linkend="sec:gradle_properties_and_system_properties"/>).
         </para>
@@ -93,21 +93,21 @@ gradle.taskGraph.whenReady { taskGraph ->
     <section>
         <title>Specifying what to sign</title>
         <para>
-            As well as configuring how things are to be signed (i.e. the signatory configuration), you must also specify what is to be signed. 
+            As well as configuring how things are to be signed (i.e. the signatory configuration), you must also specify what is to be signed.
             The Signing plugin provides a DSL that allows you to specify the tasks and/or configurations that should be signed.
         </para>
         <section>
             <title>Signing Configurations</title>
             <para>
-                It is common to want to sign the artifacts of a configuration. For example, the <link linkend="java_plugin">Java plugin</link> 
-                configures a jar to build and this jar artifact is added to the <literal>archives</literal> configuration. 
+                It is common to want to sign the artifacts of a configuration. For example, the <link linkend="java_plugin">Java plugin</link>
+                configures a jar to build and this jar artifact is added to the <literal>archives</literal> configuration.
                 Using the Signing DSL, you can specify that all of the artifacts of this configuration should be signed.
             </para>
             <sample id="signingArchives" dir="signing/maven" title="Signing a configuration">
                 <sourcefile file="build.gradle" snippet="sign-archives"/>
             </sample>
             <para>
-                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signArchives</literal>”, 
+                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signArchives</literal>”,
                 that will build any <literal>archives</literal> artifacts (if needed) and then generate signatures for them. The signature files will be placed
                 alongside the artifacts being signed.
             </para>
@@ -125,7 +125,7 @@ gradle.taskGraph.whenReady { taskGraph ->
                 <sourcefile file="build.gradle" snippet="sign-task"/>
             </sample>
             <para>
-                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signStuffZip</literal>”, 
+                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signStuffZip</literal>”,
                 that will build the input task's archive (if needed) and then sign it. The signature file will be placed
                 alongside the artifact being signed.
             </para>
@@ -134,7 +134,7 @@ gradle.taskGraph.whenReady { taskGraph ->
             </sample>
             <para>
                 For a task to be “signable”, it must produce an archive of some type. Tasks that do this are the <apilink class="org.gradle.api.tasks.bundling.Tar"/>,
-                <apilink class="org.gradle.api.tasks.bundling.Zip"/>, <apilink class="org.gradle.api.tasks.bundling.Jar"/>, 
+                <apilink class="org.gradle.api.tasks.bundling.Zip"/>, <apilink class="org.gradle.api.tasks.bundling.Jar"/>,
                 <apilink class="org.gradle.api.tasks.bundling.War"/> and <apilink class="org.gradle.plugins.ear.Ear"/> tasks.
             </para>
         </section>
@@ -154,7 +154,7 @@ gradle.taskGraph.whenReady { taskGraph ->
             </para>
         </section>
     </section>
-    
+
     <section>
         <title>Publishing the signatures</title>
         <para>
@@ -163,7 +163,7 @@ gradle.taskGraph.whenReady { taskGraph ->
             with the artifacts you simply execute the <literal>uploadArchives</literal> task as normal.
         </para>
     </section>
-    
+
     <section>
         <title>Signing POM files</title>
         <para>
@@ -179,5 +179,5 @@ gradle.taskGraph.whenReady { taskGraph ->
             <literal>signPom()</literal> method will silently do nothing.
         </para>
     </section>
-    
+
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
index 0608bab..58e0c7c 100644
--- a/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
@@ -14,64 +14,64 @@
   ~ limitations under the License.
   -->
 <chapter id="sonar_runner_plugin">
-    <title>The Sonar Runner Plugin</title>
+    <title>The SonarQube Runner Plugin</title>
     <note>
         <para>
-            The Sonar Runner plugin is currently <link linkend="feature_lifecycle">incubating</link>.
+            The SonarQube 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>
         <para>
             It is intended that this plugin will replace the older <link linkend="sonar_plugin">Sonar Plugin</link> in a future Gradle version.
         </para>
     </note>
-    <para>The Sonar Runner plugin provides integration with <ulink url="http://www.sonarsource.org">Sonar</ulink>,
-        a web-based platform for monitoring code quality. It is based on the <ulink url="http://docs.codehaus.org/display/SONAR/Analyzing+with+SonarQube+Runner">Sonar Runner</ulink>,
-        a Sonar client component that analyzes source code and build outputs, and stores all collected information in the Sonar database.
-        Compared to using the standalone Sonar Runner, the Sonar Runner plugin offers the following benefits:
+    <para>The SonarQube Runner plugin provides integration with <ulink url="http://www.sonarqube.org/">SonarQube</ulink>,
+        a web-based platform for monitoring code quality. It is based on the <ulink url="http://redirect.sonarsource.com/doc/analyzing-with-sq-runner.html">SonarQube Runner API</ulink>,
+        a SonarQube library that starts source code analysis, and optionally publish all collected information to the SonarQube server.
+        Compared to using the standalone SonarQube Runner CLI, the Gradle SonarQube Runner plugin offers the following benefits:
     </para>
     <variablelist>
         <varlistentry>
-            <term>Automatic provisioning of Sonar Runner</term>
+            <term>Automatic provisioning of SonarQube Runner</term>
             <listitem>
-                <para>The ability to execute the Sonar Runner via a regular Gradle task makes it available anywhere Gradle is available
-                    (developer build, CI server, etc.), without the need to manually download, setup, and maintain a Sonar Runner installation.</para>
+                <para>The ability to execute the SonarQube Runner via a regular Gradle task makes it available anywhere Gradle is available
+                    (developer build, CI server, etc.), without the need to manually download, setup, and maintain a SonarQube Runner installation.</para>
             </listitem>
         </varlistentry>
         <varlistentry>
             <term>Dynamic configuration from Gradle build scripts</term>
             <listitem>
-                <para>All of Gradle's scripting features can be leveraged to configure Sonar Runner as needed.</para>
+                <para>All of Gradle's scripting features can be leveraged to configure SonarQube Runner as needed.</para>
             </listitem>
         </varlistentry>
         <varlistentry>
             <term>Extensive configuration defaults</term>
             <listitem>
-                <para>Gradle already has much of the information needed for Sonar Runner to successfully analyze a project. By preconfiguring
-                    the Sonar Runner based on that information, the need for manual configuration is reduced significantly.</para>
+                <para>Gradle already has much of the information needed for SonarQube to successfully analyze a project. By preconfiguring
+                    the SonarQube Runner properties based on that information, the need for manual configuration is reduced significantly.</para>
             </listitem>
         </varlistentry>
     </variablelist>
 
     <section>
-        <title>Sonar Runner version and compatibility</title>
+        <title>SonarQube Runner version and compatibility</title>
         <para>
-            The default version of the Sonar Runner used by the plugin is 2.3, which makes it compatible with Sonar 3.0 and higher.
-            For compatibility with Sonar versions earlier than 3.0, you can configure the use of an earlier Sonar Runner version (see <xref linkend="sec:specify_sonar_runner_version"/>).
+            The default version of the SonarQube Runner used by the plugin is 2.3, which makes it compatible with SonarQube 3.0 and higher.
+            For compatibility with SonarQube versions earlier than 3.0, you can configure the use of an earlier SonarQube Runner version (see <xref linkend="sec:specify_sonar_runner_version"/>).
         </para>
     </section>
     <section>
         <title>Getting started</title>
-        <para>To get started, apply the Sonar Runner plugin to the project to be analyzed.</para>
-        <sample id="quickstart" dir="sonarRunner/quickstart" title="Applying the Sonar Runner plugin">
+        <para>To get started, apply the SonarQube Runner plugin to the project to be analyzed.</para>
+        <sample id="quickstart" dir="sonarRunner/quickstart" title="Applying the SonarQube Runner plugin">
             <sourcefile file="build.gradle" snippet="apply-plugin"/>
         </sample>
         <para>
-            Assuming a local Sonar server with out-of-the-box settings is up and running, no further mandatory configuration is required.
+            Assuming a local SonarQube server with out-of-the-box settings is up and running, no further mandatory configuration is required.
             Execute <userinput>gradle sonarRunner</userinput> and wait until the build has completed, then open the web page indicated
-            at the bottom of the Sonar Runner output. You should now be able to browse the analysis results.
+            at the bottom of the SonarQube Runner output. You should now be able to browse the analysis results.
         </para>
         <para>
-            Before executing the <literal>sonarRunner</literal> task, all tasks producing output to be analysed by Sonar need to be executed.
+            Before executing the <literal>sonarRunner</literal> task, all tasks producing output to be analysed by SonarQube need to be executed.
             Typically, these are compile tasks, test tasks, and code coverage tasks. To meet these needs, the plugins adds a task dependency
             from <literal>sonarRunner</literal> on <literal>test</literal> if the <literal>java</literal> plugin is applied. Further task dependencies can be
             added as needed.
@@ -79,33 +79,33 @@
     </section>
 
     <section>
-        <title>Configuring the Sonar Runner</title>
-        <para>The Sonar Runner plugin adds a <apilink class="org.gradle.sonar.runner.SonarRunnerRootExtension" /> extension to the project and a
+        <title>Configuring the SonarQube Runner</title>
+        <para>The SonarQube Runner plugin adds a <apilink class="org.gradle.sonar.runner.SonarRunnerRootExtension" /> extension to the project and a
             <apilink class="org.gradle.sonar.runner.SonarRunnerExtension" /> extension to its subprojects,
-            which allows you to configure the Sonar Runner via key/value pairs known as <firstterm>Sonar properties</firstterm>. A typical base line configuration
-            includes connection settings for the Sonar server and database.
+            which allows you to configure the SonarQube Runner via key/value pairs known as <firstterm>SonarQube properties</firstterm>. A typical base line configuration
+            includes connection settings for the SonarQube server and database.
         </para>
-        <sample id="quickstart" dir="sonarRunner/quickstart" title="Configuring Sonar connection settings">
+        <sample id="quickstart" dir="sonarRunner/quickstart" title="Configuring SonarQube connection settings">
             <sourcefile file="build.gradle" snippet="connection-settings"/>
         </sample>
         <para>
-            Alternatively, Sonar properties can be set from the command line. See <xref linkend="sec:sonar_command_line_parameters" /> for more information.
+            Alternatively, SonarQube properties can be set from the command line. See <xref linkend="sec:sonar_command_line_parameters" /> for more information.
         </para>
         <para>
-            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.
+            For a complete list of standard SonarQube properties, consult the <ulink url="http://redirect.sonarsource.com/doc/analyzing-with-sq-runner.html">SonarQube documentation</ulink>.
+            If you happen to use additional SonarQube plugins, consult their documentation.
         </para>
-        <para>In addition to set Sonar properties, the <apilink class="org.gradle.sonar.runner.SonarRunnerRootExtension" /> extension allows the configuration of the Sonar Runner version and
-            the <apilink class="org.gradle.process.JavaForkOptions" /> of the forked Sonar Runner process.
+        <para>In addition to set SonarQube properties, the <apilink class="org.gradle.sonar.runner.SonarRunnerRootExtension" /> extension allows the configuration of the SonarQube Runner version and
+            the <apilink class="org.gradle.process.JavaForkOptions" /> of the forked SonarQube Runner process.
         </para>
         <para>
-            The Sonar Runner plugin leverages information contained in Gradle's object model to provide smart defaults for many of the standard Sonar properties.
+            The SonarQube Runner plugin leverages information contained in Gradle's object model to provide smart defaults for many of the standard SonarQube properties.
             The defaults are summarized in the tables below. Notice that additional defaults are provided for projects that have the <literal>java-base</literal>
             or <literal>java</literal> plugin applied. For some properties (notably server and database connection settings), determining a suitable default
-            is left to the Sonar Runner.
+            is left to the SonarQube Runner.
         </para>
         <table>
-            <title>Gradle defaults for standard Sonar properties</title>
+            <title>Gradle defaults for standard SonarQube properties</title>
             <thead>
                 <tr>
                     <td>Property</td>
@@ -114,7 +114,7 @@
             </thead>
             <tr>
                 <td>sonar.projectKey</td>
-                <td>“$project.group:$project.name” (for root project of analysed hierarchy; left to Sonar Runner otherwise)</td>
+                <td>“$project.group:$project.name” (for root project of analysed hierarchy; left to SonarQube Runner otherwise)</td>
             </tr>
             <tr>
                 <td>sonar.projectName</td>
@@ -206,28 +206,28 @@
         </table>
     </section>
     <section id="sec:specify_sonar_runner_version">
-        <title>Specifying the Sonar Runner version</title>
+        <title>Specifying the SonarQube Runner version</title>
         <para>
-            By default, version 2.3 of the Sonar Runner is used.
+            By default, version 2.3 of the SonarQube Runner is used.
             To specify an alternative version, set the <apilink class="org.gradle.sonar.runner.SonarRunnerRootExtension" method="getToolVersion()"/> property
             of the <literal>sonarRunner</literal> extension of the project the plugin was applied to to the desired version.
-            This will result in the Sonar Runner dependency <literal>org.codehaus.sonar.runner:sonar-runner-dist:«toolVersion»</literal> being used as the Sonar Runner.
+            This will result in the SonarQube Runner dependency <literal>org.codehaus.sonar.runner:sonar-runner-dist:«toolVersion»</literal> being used as the SonarQube Runner.
         </para>
-        <sample id="quickstart" dir="sonarRunner/quickstart" title="Configuring Sonar runner version">
+        <sample id="quickstart" dir="sonarRunner/quickstart" title="Configuring SonarQube runner version">
             <sourcefile file="build.gradle" snippet="version-settings"/>
         </sample>
     </section>
     <section>
         <title>Analyzing Multi-Project Builds</title>
-        <para>The Sonar Runner is capable of analyzing whole project hierarchies at once. This yields a hierarchical view in the
-            Sonar web interface, with aggregated metrics and the ability to drill down into subprojects. Analyzing a project hierarchy
+        <para>The SonarQube Runner is capable of analyzing whole project hierarchies at once. This yields a hierarchical view in the
+            SonarQube web interface, with aggregated metrics and the ability to drill down into subprojects. Analyzing a project hierarchy
             also takes less time than analyzing each project separately.
         </para>
         <para>
-            To analyze a project hierarchy, apply the Sonar Runner plugin to the root project of the hierarchy.
+            To analyze a project hierarchy, apply the SonarQube Runner plugin to the root project of the hierarchy.
             Typically (but not necessarily) this will be the root project of the Gradle build. Information pertaining to the
             analysis as a whole, like server and database connections settings, have to be configured in the <literal>sonarRunner</literal>
-            block of this project. Any Sonar properties set on the command line also apply to this project.
+            block of this project. Any SonarQube properties set on the command line also apply to this project.
         </para>
         <sample id="multiProject" dir="sonarRunner/multiProject" title="Global configuration settings">
             <sourcefile file="build.gradle" snippet="global-configuration-settings"/>
@@ -246,7 +246,7 @@
             <sourcefile file="build.gradle" snippet="individual-configuration-settings"/>
         </sample>
         <para>
-            To skip Sonar analysis for a particular subproject, set <literal>sonarRunner.skipProject</literal> to <literal>true</literal>.
+            To skip SonarQube analysis for a particular subproject, set <literal>sonarRunner.skipProject</literal> to <literal>true</literal>.
         </para>
         <sample id="multiProject" dir="sonarRunner/multiProject" title="Skipping analysis of a project">
             <sourcefile file="build.gradle" snippet="skip-project"/>
@@ -255,7 +255,7 @@
 
     <section>
         <title>Analyzing Custom Source Sets</title>
-        <para>By default, the Sonar Runner plugin passes on the project's <literal>main</literal> source set as production sources, and the
+        <para>By default, the SonarQube Runner plugin passes on the project's <literal>main</literal> source set as production sources, and the
              project's <literal>test</literal> source set as test sources. This works regardless of the project's source directory layout.
              Additional source sets can be added as needed.
         </para>
@@ -267,21 +267,18 @@
     <section>
         <title>Analyzing languages other than Java</title>
         <para>
-            To analyze code written in a language other than Java, you'll need to set <literal>sonar.project.language</literal> accordingly.
-            However, note that your Sonar server has to have the <ulink url="http://www.sonarsource.com/products/plugins/languages/">Sonar plugin</ulink>
-            that handles that programming language.
+            As of SonarQube 4.2, multi-language projects are supported and each file language will be detected according to its filename suffix.
+            However, note that your SonarQube server has to have the <ulink url="http://www.sonarsource.com/products/plugins/languages/">SonarQube plugin</ulink>
+            that handles that programming language. If you
+            want to enforce a single language for your project, you'll need to set <literal>sonar.project.language</literal> accordingly.
         </para>
         <sample id="advanced" dir="sonarRunner/advanced" title="Analyzing languages other than Java">
             <sourcefile file="build.gradle" snippet="languages" />
         </sample>
-        <para>
-            As of Sonar 3.4, only one language per project can be analyzed. It is, however, possible to analyze a different language
-            for each project in a multi-project build.
-        </para>
     </section>
 
     <section>
-        <title>More on configuring Sonar properties</title>
+        <title>More on configuring SonarQube properties</title>
         <para>
             Let's take a closer look at the <literal>sonarRunner.sonarProperties {}</literal> block. As we have already seen in the examples,
             the <literal>property()</literal> method allows you to set new properties or override existing ones. Furthermore, all properties that have
@@ -301,9 +298,9 @@
     </section>
 
     <section id="sec:sonar_command_line_parameters">
-        <title>Setting Sonar Properties from the Command Line</title>
+        <title>Setting SonarQube Properties from the Command Line</title>
         <para>
-            Sonar Properties can also be set from the command line, by setting a system property named exactly like the Sonar property in question.
+            SonarQube Properties can also be set from the command line, by setting a system property named exactly like the Sonar property in question.
             This can be useful when dealing with sensitive information (e.g. credentials), environment information, or for ad-hoc configuration.
         </para>
         <programlisting>
@@ -315,22 +312,22 @@
                 available to everyone.
             </para>
         </note>
-        <para>A Sonar property value set via a system property overrides any value set in a build script (for the same property). When
+        <para>A SonarQube property value set via a system property overrides any value set in a build script (for the same property). When
             analyzing a project hierarchy, values set via system properties apply to the root project of the analyzed hierarchy.
             Each system property starting with "<literal>"sonar."</literal> will taken into account for the sonar runner setup.
         </para>
     </section>
 
     <section>
-        <title>Controlling the Sonar Runner process</title>
+        <title>Controlling the SonarQube Runner process</title>
         <para>
-            The Sonar Runner is executed in a forked process.
-            This allows fine grained control over memory settings, system properties etc. just for the Sonar Runner process.
+            The SonarQube Runner is executed in a forked process.
+            This allows fine grained control over memory settings, system properties etc. just for the SonarQube Runner process.
             The <literal>forkOptions</literal> property of the <literal>sonarRunner</literal> extension of the project that applies the <literal>sonar-runner</literal> plugin
             (Usually the <literal>rootProject</literal> but not necessarily) allows the process configuration to be specified.
             This property is not available in the <apilink class="org.gradle.sonar.runner.SonarRunnerExtension"/> extension applied to the subprojects.
         </para>
-        <sample id="advanced" dir="sonarRunner/advanced" title="setting custom Sonar Runner fork options">
+        <sample id="advanced" dir="sonarRunner/advanced" title="setting custom SonarQube Runner fork options">
             <sourcefile file="build.gradle" snippet="forkoptions"/>
         </sample>
         <para>
@@ -340,9 +337,9 @@
 
     <section>
         <title>Tasks</title>
-        <para>The Sonar Runner plugin adds the following tasks to the project.</para>
+        <para>The SonarQube Runner plugin adds the following tasks to the project.</para>
         <table>
-            <title>Sonar Runner plugin - tasks</title>
+            <title>SonarQube Runner plugin - tasks</title>
             <thead>
                 <tr>
                     <td>Task name</td>
@@ -355,7 +352,7 @@
                 <td><literal>sonarRunner</literal></td>
                 <td>-</td>
                 <td><apilink class="org.gradle.sonar.runner.tasks.SonarRunner"/></td>
-                <td>Analyzes a project hierarchy and stores the results in the Sonar database.</td>
+                <td>Analyzes a project hierarchy with SonarQube.</td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index 32fdb12..4722ce0 100755
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -45,6 +45,7 @@
     <xi:include href='ant.xml'/>
     <xi:include href='logging.xml'/>
     <xi:include href='gradleDaemon.xml'/>
+    <xi:include href='continuousBuild.xml'/>
     <xi:include href='buildEnvironment.xml'/>
     <xi:include href='plugins.xml'/>
     <xi:include href='standardPlugins.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index f498b5e..70dd272 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -219,7 +219,7 @@
         <sample id="copy" dir="userguide/files/copy" title="Selecting the files to copy">
             <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
+        <para>You can also use the <apilink class="org.gradle.api.Project" method="copy(org.gradle.api.Action)"/> method to copy files. It works the
             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>
@@ -410,7 +410,7 @@
 
         <section>
             <title>Sharing content between multiple archives</title>
-            <para>You can use 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(org.gradle.api.Action)"/> 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
diff --git a/subprojects/docs/src/samples/antlr/build.gradle b/subprojects/docs/src/samples/antlr/build.gradle
index d797ee4..697dc2e 100644
--- a/subprojects/docs/src/samples/antlr/build.gradle
+++ b/subprojects/docs/src/samples/antlr/build.gradle
@@ -10,14 +10,16 @@ repositories {
 
 dependencies {
     antlr "org.antlr:antlr:3.5.2" // use ANTLR version 3
+    // antlr "org.antlr:antlr4:4.5" // use ANTLR version 4
 // END SNIPPET declare-dependency
     testCompile 'junit:junit:4.12'
 // START SNIPPET declare-dependency
 }
 // END SNIPPET declare-dependency
 
-// START SNIPPET memory-settings
+// START SNIPPET generate-grammar-settings
 generateGrammarSource {
     maxHeapSize = "64m"
+    arguments += ["-visitor", "-long-messages"]
 }
-// END SNIPPET memory-settings
+// END SNIPPET generate-grammar-settings
diff --git a/subprojects/docs/src/samples/customModel/componentType/build.gradle b/subprojects/docs/src/samples/customModel/componentType/build.gradle
index 23493f4..070ba6b 100644
--- a/subprojects/docs/src/samples/customModel/componentType/build.gradle
+++ b/subprojects/docs/src/samples/customModel/componentType/build.gradle
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
+import org.gradle.model.ModelMap
 import org.gradle.model.RuleSource
-import org.gradle.model.collection.CollectionBuilder
 
 interface ImageComponent extends LibrarySpec {
     String title
@@ -50,7 +50,7 @@ class MyImageRenderingPlugin extends RuleSource {
     }
 
     @ComponentBinaries
-    void createBinariesForBinaryComponent(CollectionBuilder<ImageBinary> binaries, ImageComponent library) {
+    void createBinariesForBinaryComponent(ModelMap<ImageBinary> binaries, ImageComponent library) {
         library.sizes.each{ fontSize ->
             binaries.create("${library.title}${fontSize}Binary"){
                 it.size = fontSize;
@@ -60,7 +60,7 @@ class MyImageRenderingPlugin extends RuleSource {
     }
 
     @BinaryTasks
-    void createRenderingTasks(CollectionBuilder<Task> tasks, ImageBinary binary) {
+    void createRenderingTasks(ModelMap<Task> tasks, ImageBinary binary) {
         tasks.create("render${binary.title}${binary.size}Svg", RenderSvg){
             it.content = binary.title;
             it.fontSize = binary.size;
diff --git a/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/documentation/DocumentationPlugin.groovy b/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/documentation/DocumentationPlugin.groovy
index 968132b..fd15c7b 100644
--- a/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/documentation/DocumentationPlugin.groovy
+++ b/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/documentation/DocumentationPlugin.groovy
@@ -18,10 +18,10 @@ package sample.documentation
 import org.gradle.api.Action
 import org.gradle.api.Task
 import org.gradle.api.tasks.bundling.Zip
+import org.gradle.model.ModelMap
 import org.gradle.model.Mutate
 import org.gradle.model.Path
 import org.gradle.model.RuleSource
-import org.gradle.model.collection.CollectionBuilder
 import org.gradle.platform.base.*
 
 class DocumentationPlugin extends RuleSource {
@@ -36,12 +36,12 @@ class DocumentationPlugin extends RuleSource {
     }
 
     @ComponentBinaries
-    void createBinariesForBinaryComponent(CollectionBuilder<DocumentationBinary> binaries, DocumentationComponent component) {
+    void createBinariesForBinaryComponent(ModelMap<DocumentationBinary> binaries, DocumentationComponent component) {
         binaries.create("${component.name}Binary")
     }
 
     @BinaryTasks
-    void createZip(CollectionBuilder<Task> tasks, final DocumentationBinary binary, @Path("buildDir") final File buildDir) {
+    void createZip(ModelMap<Task> tasks, final DocumentationBinary binary, @Path("buildDir") final File buildDir) {
         tasks.create("zip${binary.name.capitalize()}", Zip, new Action<Zip>() {
             @Override
             public void execute(Zip zipBinary) {
@@ -56,4 +56,4 @@ class DocumentationPlugin extends RuleSource {
             }
         });
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/markdown/MarkdownPlugin.groovy b/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/markdown/MarkdownPlugin.groovy
index e50407d..dddcec0 100644
--- a/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/markdown/MarkdownPlugin.groovy
+++ b/subprojects/docs/src/samples/customModel/languageType/buildSrc/src/main/groovy/sample/markdown/MarkdownPlugin.groovy
@@ -17,9 +17,9 @@
 package sample.markdown
 
 import org.gradle.model.Defaults
+import org.gradle.model.ModelMap
 import org.gradle.model.Path
 import org.gradle.model.RuleSource
-import org.gradle.model.collection.CollectionBuilder
 import org.gradle.platform.base.LanguageType
 import org.gradle.platform.base.LanguageTypeBuilder
 import sample.documentation.DocumentationBinary
@@ -32,7 +32,7 @@ class MarkdownPlugin extends RuleSource {
     }
 
     @Defaults
-    void createMarkdownHtmlCompilerTasks(CollectionBuilder<DocumentationBinary> binaries, @Path("buildDir") File buildDir) {
+    void createMarkdownHtmlCompilerTasks(ModelMap<DocumentationBinary> binaries, @Path("buildDir") File buildDir) {
         binaries.beforeEach { binary ->
             source.withType(MarkdownSourceSet.class) { markdownSourceSet ->
                 taskName = binary.name + name.capitalize() + "HtmlCompile"
@@ -44,4 +44,4 @@ class MarkdownPlugin extends RuleSource {
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/docs/src/samples/dependency-substitution/build.gradle b/subprojects/docs/src/samples/dependency-substitution/build.gradle
new file mode 100644
index 0000000..df3aecf
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/build.gradle
@@ -0,0 +1,49 @@
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+
+/*
+ * This sample demonstrates the ability to selectively include projects
+ * from the local directory rather than using an external dependency.
+ *
+ * By default all projects are considered external and are picked up
+ * from the "repo" ivy repository.  To include local projects in a build,
+ * set the "useLocal" system property on the gradle command line:
+ *
+ *   gradle -DuseLocal=project1,project2 :showJarFiles
+ *
+ */
+
+allprojects {
+    apply plugin: 'java'
+
+    group "org.example"
+    version "1.0"
+
+    def repoUrl = "file://${rootProject.projectDir}/repo"
+
+    repositories {
+        ivy { url repoUrl }
+    }
+
+    //START SNIPPET project_substitution
+    configurations.all {
+        resolutionStrategy.dependencySubstitution.all { DependencySubstitution dependency ->
+            if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == "org.example") {
+                def targetProject = findProject(":${dependency.requested.module}")
+                if (targetProject != null) {
+                    dependency.useTarget targetProject
+                }
+            }
+        }
+    }
+    //END SNIPPET project_substitution
+}
+
+dependencies {
+   compile "org.example:project1:1.0"
+}
+
+task showJarFiles {
+    doLast {
+        configurations.compile.files.each { println it.path - rootDir.path }
+    }
+}
diff --git a/subprojects/docs/src/samples/dependency-substitution/project1/build.gradle b/subprojects/docs/src/samples/dependency-substitution/project1/build.gradle
new file mode 100644
index 0000000..c35c1d0
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/project1/build.gradle
@@ -0,0 +1,3 @@
+dependencies {
+    compile "org.example:project2:1.0"
+}
diff --git a/subprojects/docs/src/samples/dependency-substitution/project2/build.gradle b/subprojects/docs/src/samples/dependency-substitution/project2/build.gradle
new file mode 100644
index 0000000..ed9b147
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/project2/build.gradle
@@ -0,0 +1,3 @@
+dependencies {
+    compile "org.example:project3:1.0"
+}
diff --git a/subprojects/docs/src/samples/dependency-substitution/project3/build.gradle b/subprojects/docs/src/samples/dependency-substitution/project3/build.gradle
new file mode 100644
index 0000000..7d82dc7
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/project3/build.gradle
@@ -0,0 +1,2 @@
+dependencies {
+}
diff --git a/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project1/1.0/ivy-1.0.xml b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project1/1.0/ivy-1.0.xml
new file mode 100644
index 0000000..3fef2e2
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project1/1.0/ivy-1.0.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
+  <info organisation="org.example" module="project1" revision="1.0" status="integration" publication="20150519174136">
+    <description/>
+  </info>
+  <configurations>
+    <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
+    <conf name="archives" visibility="public" description="Configuration for archive artifacts."/>
+    <conf name="compile" visibility="private" description="Compile classpath for source set 'main'."/>
+    <conf name="testRuntime" visibility="private" description="Runtime classpath for source set 'test'." extends="runtime,testCompile"/>
+    <conf name="runtime" visibility="private" description="Runtime classpath for source set 'main'." extends="compile"/>
+    <conf name="testCompile" visibility="private" description="Compile classpath for source set 'test'." extends="compile"/>
+  </configurations>
+  <publications>
+    <artifact name="project1" type="jar" ext="jar" conf="archives,runtime"/>
+  </publications>
+  <dependencies>
+    <dependency org="org.example" name="project2" rev="1.0" conf="compile->default"/>
+  </dependencies>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project2/1.0/ivy-1.0.xml b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project2/1.0/ivy-1.0.xml
new file mode 100644
index 0000000..03df96e
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project2/1.0/ivy-1.0.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
+  <info organisation="org.example" module="project2" revision="1.0" status="integration" publication="20150519174136">
+    <description/>
+  </info>
+  <configurations>
+    <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
+    <conf name="archives" visibility="public" description="Configuration for archive artifacts."/>
+    <conf name="compile" visibility="private" description="Compile classpath for source set 'main'."/>
+    <conf name="testRuntime" visibility="private" description="Runtime classpath for source set 'test'." extends="runtime,testCompile"/>
+    <conf name="runtime" visibility="private" description="Runtime classpath for source set 'main'." extends="compile"/>
+    <conf name="testCompile" visibility="private" description="Compile classpath for source set 'test'." extends="compile"/>
+  </configurations>
+  <publications>
+    <artifact name="project2" type="jar" ext="jar" conf="archives,runtime"/>
+  </publications>
+  <dependencies>
+    <dependency org="org.example" name="project3" rev="1.0" conf="compile->default"/>
+  </dependencies>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project3/1.0/ivy-1.0.xml b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project3/1.0/ivy-1.0.xml
new file mode 100644
index 0000000..501fbc1
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/repo/org.example/project3/1.0/ivy-1.0.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
+  <info organisation="org.example" module="project3" revision="1.0" status="integration" publication="20150519174136">
+    <description/>
+  </info>
+  <configurations>
+    <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
+    <conf name="archives" visibility="public" description="Configuration for archive artifacts."/>
+    <conf name="compile" visibility="private" description="Compile classpath for source set 'main'."/>
+    <conf name="testRuntime" visibility="private" description="Runtime classpath for source set 'test'." extends="runtime,testCompile"/>
+    <conf name="runtime" visibility="private" description="Runtime classpath for source set 'main'." extends="compile"/>
+    <conf name="testCompile" visibility="private" description="Compile classpath for source set 'test'." extends="compile"/>
+  </configurations>
+  <publications>
+    <artifact name="project3" type="jar" ext="jar" conf="archives,runtime"/>
+  </publications>
+</ivy-module>
diff --git a/subprojects/docs/src/samples/dependency-substitution/settings.gradle b/subprojects/docs/src/samples/dependency-substitution/settings.gradle
new file mode 100644
index 0000000..092ed14
--- /dev/null
+++ b/subprojects/docs/src/samples/dependency-substitution/settings.gradle
@@ -0,0 +1,18 @@
+ext.projectNames = ["project1", "project2", "project3"]
+
+projectNames.each { name ->
+    if (isIncluded(name)) {
+        println "project $name is INTERNAL to this build"
+        include name
+    } else {
+        println "project $name is external to this build"
+    }
+}
+
+def isIncluded(String projectName) {
+    if (System.getProperty("useLocal") != null) {
+        def localProjects = System.getProperty("useLocal").split(",")
+        return (projectName in localProjects)
+    }
+    return false
+}
diff --git a/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/build.gradle b/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/build.gradle
index cfec515..2443470 100644
--- a/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/build.gradle
+++ b/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/build.gradle
@@ -20,7 +20,7 @@ class PersonRules extends RuleSource {
 // END SNIPPET plugin-mutate-rule
 
 // START SNIPPET task-create-rule
- @Mutate void createHelloTask(CollectionBuilder<Task> tasks, Person p) {
+ @Mutate void createHelloTask(ModelMap<Task> tasks, Person p) {
     tasks.create("hello") {
       doLast {
         println "Hello $p.firstName $p.lastName!"
diff --git a/subprojects/docs/src/samples/modelRules/modelDsl/build.gradle b/subprojects/docs/src/samples/modelRules/modelDsl/build.gradle
index a5dd007..791f119 100644
--- a/subprojects/docs/src/samples/modelRules/modelDsl/build.gradle
+++ b/subprojects/docs/src/samples/modelRules/modelDsl/build.gradle
@@ -5,7 +5,7 @@ interface Person {
 }
 
 class PersonRules extends RuleSource {
- @Mutate void createHelloTask(CollectionBuilder<Task> tasks, Person p) {
+ @Mutate void createHelloTask(ModelMap<Task> tasks, Person p) {
     tasks.create("hello") {
       doLast {
         println "Hello $p.firstName $p.lastName!"
diff --git a/subprojects/docs/src/samples/native-binaries/google-test/build.gradle b/subprojects/docs/src/samples/native-binaries/google-test/build.gradle
index 6622836..98d5939 100644
--- a/subprojects/docs/src/samples/native-binaries/google-test/build.gradle
+++ b/subprojects/docs/src/samples/native-binaries/google-test/build.gradle
@@ -37,6 +37,11 @@ binaries.withType(GoogleTestTestSuiteBinarySpec) {
     if (flavor == flavors.failing) {
         cppCompiler.define "PLUS_BROKEN"
     }
+
+    if (targetPlatform.operatingSystem.linux) {
+        cppCompiler.args '-pthread'
+        linker.args '-pthread'
+    }
 }
 // END SNIPPET configure-test-binary
 // END SNIPPET complete-example
diff --git a/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/build.gradle b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/build.gradle
new file mode 100644
index 0000000..6785045
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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: 'cpp'
+// END SNIPPET apply-plugin
+
+// START SNIPPET libraries
+model {
+    components {
+        hello(NativeLibrarySpec) {
+            sources {
+                cpp {
+                    preCompiledHeader "pch.h"
+                }
+            }
+        }
+    }
+}
+// END SNIPPET libraries
+
+// START SNIPPET executables
+model {
+    components {
+        main(NativeExecutableSpec) {
+// START SNIPPET source-library
+            sources {
+                cpp {
+                    lib library: "hello"
+                }
+            }
+// END SNIPPET source-library
+        }
+    }
+}
+
+// END SNIPPET executables
+
+// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
+binaries.withType(SharedLibraryBinarySpec) {
+    if (toolChain in VisualCpp) {
+        cppCompiler.define "DLL_EXPORT"
+    }
+}
+
+
diff --git a/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/cpp/hello.cpp b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/cpp/hello.cpp
new file mode 100755
index 0000000..07ff5c4
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/cpp/hello.cpp
@@ -0,0 +1,5 @@
+#include "pch.h"
+
+void LIB_FUNC Greeter::hello () {
+    std::cout << "Hello world!" << std::endl;
+}
diff --git a/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/hello.h b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/hello.h
new file mode 100755
index 0000000..6686518
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/hello.h
@@ -0,0 +1,13 @@
+#ifndef HELLO_H
+#define HELLO_H
+#if defined(_WIN32) && defined(DLL_EXPORT)
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+class Greeter {
+    public:
+    void LIB_FUNC hello();
+};
+#endif
diff --git a/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/pch.h b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/pch.h
new file mode 100644
index 0000000..026cc40
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/hello/headers/pch.h
@@ -0,0 +1,5 @@
+#ifndef PCH_H
+#define PCH_H
+#include <iostream>
+#include "hello.h"
+#endif
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/main/cpp/main.cpp b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/main/cpp/main.cpp
new file mode 100644
index 0000000..e35c88f
--- /dev/null
+++ b/subprojects/docs/src/samples/native-binaries/pre-compiled-headers/src/main/cpp/main.cpp
@@ -0,0 +1,7 @@
+#include "hello.h"
+
+int main () {
+    Greeter greeter;
+    greeter.hello();
+    return 0;
+}
diff --git a/subprojects/docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle
index cc15d61..b9eddec 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle
@@ -23,3 +23,13 @@ configurations.compile {
     description = 'compile classpath'
 }
 // END SNIPPET configure-configuration
+
+//START SNIPPET configuration-default-dependencies
+configurations {
+    pluginTool {
+        defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("org.gradle:my-util:1.0"))
+        }
+    }
+}
+//END SNIPPET configuration-default-dependencies
diff --git a/subprojects/docs/src/samples/userguide/artifacts/dependency-substitution/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/dependency-substitution/build.gradle
new file mode 100644
index 0000000..43d6419
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/dependency-substitution/build.gradle
@@ -0,0 +1,16 @@
+
+//START SNIPPET module_to_project_substitution
+configurations.all {
+    resolutionStrategy.dependencySubstitution {
+        substitute module("org.utils:api") with project(":api")
+        substitute module("org.utils:util:2.5") with project(":util")
+    }
+}
+//END SNIPPET module_to_project_substitution
+//START SNIPPET project_to_module_substitution
+configurations.all {
+    resolutionStrategy.dependencySubstitution {
+        substitute project(":api") with module("org.utils:api:1.3")
+    }
+}
+//END SNIPPET project_to_module_substitution
diff --git a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
index 1702439..568a79e 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
@@ -12,7 +12,7 @@ configurations.all {
 
 //START SNIPPET releasable-unit
 configurations.all {
-    resolutionStrategy.dependencySubstitution.eachModule { ModuleDependencySubstitution details ->
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
         if (details.requested.group == 'org.gradle') {
             details.useVersion '1.4'
         }
@@ -22,9 +22,9 @@ configurations.all {
 
 //START SNIPPET custom-versioning-scheme
 configurations.all {
-    resolutionStrategy.dependencySubstitution.eachModule { ModuleDependencySubstitution details ->
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
         if (details.requested.version == 'default') {
-            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.module)
+            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
             details.useVersion version
         }
     }
@@ -38,8 +38,8 @@ def findDefaultVersionInCatalog(String group, String name) {
 
 //START SNIPPET blacklisting_version
 configurations.all {
-    resolutionStrategy.dependencySubstitution.eachModule { ModuleDependencySubstitution details ->
-        if (details.requested.group == 'org.software' && details.requested.module == 'some-library' && details.requested.version == '1.2') {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+        if (details.requested.group == 'org.software' && details.requested.name == 'some-library' && details.requested.version == '1.2') {
             //prefer different version which contains some necessary fixes
             details.useVersion '1.2.1'
         }
@@ -49,12 +49,12 @@ configurations.all {
 
 //START SNIPPET module_substitution
 configurations.all {
-    resolutionStrategy.dependencySubstitution.eachModule { ModuleDependencySubstitution details ->
-        if (details.requested.module == 'groovy-all') {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+        if (details.requested.name == 'groovy-all') {
             //prefer 'groovy' over 'groovy-all':
             details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
         }
-        if (details.requested.module == 'log4j') {
+        if (details.requested.name == 'log4j') {
             //prefer 'log4j-over-slf4j' over 'log4j', with fixed version:
             details.useTarget "org.slf4j:log4j-over-slf4j:1.7.10"
         }
diff --git a/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle b/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle
index 8562960..e3c2557 100644
--- a/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/incrementalTask/build.gradle
@@ -47,6 +47,11 @@ class IncrementalReverseTask extends DefaultTask {
     void execute(IncrementalTaskInputs inputs) {
         println inputs.incremental ? "CHANGED inputs considered out of date"
                                    : "ALL inputs considered out of date"
+        // START SNIPPET handle-non-incremental-inputs
+        if (!inputs.incremental)
+            project.delete(outputDir.listFiles())
+        // END SNIPPET handle-non-incremental-inputs
+
         // START SNIPPET out-of-date-inputs
         inputs.outOfDate { change ->
             println "out of date: ${change.file.name}"
diff --git a/subprojects/docs/src/samples/userguideOutput/basicRuleSourcePlugin-model-task.out b/subprojects/docs/src/samples/userguideOutput/basicRuleSourcePlugin-model-task.out
index 41e63f7..f014184 100644
--- a/subprojects/docs/src/samples/userguideOutput/basicRuleSourcePlugin-model-task.out
+++ b/subprojects/docs/src/samples/userguideOutput/basicRuleSourcePlugin-model-task.out
@@ -6,5 +6,17 @@ Root project
 
 model
     person
-        firstName
-        lastName
\ No newline at end of file
+        firstName = John
+        lastName = Smith
+    tasks
+        components = task ':components'
+        dependencies = task ':dependencies'
+        dependencyInsight = task ':dependencyInsight'
+        hello = task ':hello'
+        help = task ':help'
+        init = task ':init'
+        model = task ':model'
+        projects = task ':projects'
+        properties = task ':properties'
+        tasks = task ':tasks'
+        wrapper = task ':wrapper'
diff --git a/subprojects/docs/src/samples/userguideOutput/nativeComponentReport.out b/subprojects/docs/src/samples/userguideOutput/nativeComponentReport.out
index f1590b2..fa15c4e 100644
--- a/subprojects/docs/src/samples/userguideOutput/nativeComponentReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/nativeComponentReport.out
@@ -9,7 +9,7 @@ Native library 'hello'
 
 Source sets
     C++ source 'hello:cpp'
-        src/hello/cpp
+        srcDir: src/hello/cpp
 
 Binaries
     Shared library 'hello:sharedLibrary'
@@ -32,7 +32,7 @@ Native executable 'main'
 
 Source sets
     C++ source 'main:cpp'
-        src/main/cpp
+        srcDir: src/main/cpp
 
 Binaries
     Executable 'main:executable'
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 3394528..82b2461 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
@@ -77,7 +77,7 @@ class Ear extends Jar {
         // this allows us to generate the deployment descriptor after recording all modules it contains
         def metaInf = mainSpec.addChild().into('META-INF')
         metaInf.addChild().from {
-            MapFileTree descriptorSource = new MapFileTree(temporaryDirFactory, fileSystem)
+            MapFileTree descriptorSource = new MapFileTree(temporaryDirFactory, fileSystem, MapFileTree.FileCreationMode.KEEP_EXISTING)
             final DeploymentDescriptor descriptor = deploymentDescriptor
             if (descriptor) {
                 if (!descriptor.libraryDirectory) {
diff --git a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy
index dedf32d..ca2292f 100755
--- a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy
+++ b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/visualstudio/VisualStudioSingleProjectIntegrationTest.groovy
@@ -810,6 +810,49 @@ model {
         }
     }
 
+    def "visual studio solution for executable that depends on library using precompiled header"() {
+        when:
+        app = new CppHelloWorldApp()
+        app.executable.writeSources(file("src/main"))
+        app.library.writeSources(file("src/hello"))
+        buildFile << """
+            model {
+                components {
+                    hello(NativeLibrarySpec) {
+                        sources {
+                            cpp.preCompiledHeader "pch.h"
+                        }
+                    }
+                    main(NativeExecutableSpec) {
+                        sources {
+                            cpp.lib library: '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 "visual studio solution for component graph with library dependency cycle"() {
         given:
         def app = new ExeWithLibraryUsingLibraryHelloWorldApp()
diff --git a/subprojects/ide-native/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy b/subprojects/ide-native/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy
index 077c291..c8189b4 100644
--- a/subprojects/ide-native/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy
+++ b/subprojects/ide-native/src/main/groovy/org/gradle/ide/cdt/model/CprojectSettings.groovy
@@ -76,7 +76,7 @@ class CprojectSettings {
         }
 
         def extension = ""
-        def type 
+        def type
         if (binary instanceof NativeLibrarySpec) {
             type = "org.eclipse.cdt.build.core.buildArtefactType.sharedLib"
         } else if (binary instanceof NativeExecutableSpec) {
@@ -84,7 +84,7 @@ class CprojectSettings {
         } 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
@@ -99,4 +99,4 @@ class CprojectSettings {
             conf. at buildProperties = buildPropsPairs.join(",")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ide/ide.gradle b/subprojects/ide/ide.gradle
index d8f048d..c79c386 100644
--- a/subprojects/ide/ide.gradle
+++ b/subprojects/ide/ide.gradle
@@ -22,7 +22,6 @@ dependencies {
 
     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/eclipse/AbstractEclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
index 8e1430e..1080c03 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
@@ -1,87 +1,90 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.ide.eclipse
-
-import org.gradle.integtests.fixtures.executer.ExecutionResult
-import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-
-class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
-    protected ExecutionResult runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
-        runTask("eclipse", settingsScript, buildScript)
-    }
-
-    protected File getProjectFile(Map options) {
-        getFile(options, ".project")
-    }
-
-    protected File getClasspathFile(Map options) {
-        getFile(options, ".classpath")
-    }
-
-    protected File getComponentFile(Map options) {
-        getFile(options, ".settings/org.eclipse.wst.common.component")
-    }
-
-    protected File getFacetFile(Map options) {
-        getFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
-    }
-
-    protected parseProjectFile(Map options) {
-        parseFile(options, ".project")
-    }
-
-    protected parseClasspathFile(Map options) {
-        parseFile(options, ".classpath")
-    }
-
-    protected parseComponentFile(Map options) {
-        parseFile(options, ".settings/org.eclipse.wst.common.component")
-    }
-
-    protected parseFacetFile(Map options) {
-        parseFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
-    }
-
-    protected String parseJdtFile() {
-        return getFile([:], '.settings/org.eclipse.jdt.core.prefs').text
-    }
-
-    protected findEntries(classpath, kind) {
-        classpath.classpathentry.findAll { it. at kind == kind }
-    }
-
-    protected libEntriesInClasspathFileHaveFilenames(String... filenames) {
-        def classpath = parseClasspathFile()
-        def libs = findEntries(classpath, "lib")
-        assert libs*. at path*.text().collect { new File(it).name } as Set == filenames as Set
-    }
-
-    protected EclipseWtpComponentFixture getWtpComponent() {
-        return new EclipseWtpComponentFixture(testDirectory)
-    }
-
-    protected EclipseWtpComponentFixture wtpComponent(String project) {
-        return new EclipseWtpComponentFixture(testDirectory.file(project))
-    }
-
-    protected EclipseClasspathFixture getClasspath() {
-        return new EclipseClasspathFixture(testDirectory, executer.gradleUserHomeDir)
-    }
-
-    protected EclipseClasspathFixture classpath(String path) {
-        return new EclipseClasspathFixture(testDirectory.file(path), executer.gradleUserHomeDir)
-    }
-}
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.executer.ExecutionResult
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
+
+class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
+    protected ExecutionResult runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
+        runTask("eclipse", settingsScript, buildScript)
+    }
+
+    protected File getProjectFile(Map options) {
+        getFile(options, ".project")
+    }
+
+    protected File getClasspathFile(Map options) {
+        getFile(options, ".classpath")
+    }
+
+    protected File getComponentFile(Map options) {
+        getFile(options, ".settings/org.eclipse.wst.common.component")
+    }
+
+    protected File getFacetFile(Map options) {
+        getFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
+    }
+
+    protected File getJdtPropertiesFile(Map options) {
+        getFile(options, ".settings/org.eclipse.jdt.core.prefs")
+    }
+
+    protected parseProjectFile(Map options) {
+        parseFile(options, ".project")
+    }
+
+    protected parseClasspathFile(Map options) {
+        parseFile(options, ".classpath")
+    }
+
+    protected parseComponentFile(Map options) {
+        parseFile(options, ".settings/org.eclipse.wst.common.component")
+    }
+
+    protected parseFacetFile(Map options) {
+        parseFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
+    }
+
+    protected String parseJdtFile() {
+        return getFile([:], '.settings/org.eclipse.jdt.core.prefs').text
+    }
+
+    protected findEntries(classpath, kind) {
+        classpath.classpathentry.findAll { it. at kind == kind }
+    }
+
+    protected libEntriesInClasspathFileHaveFilenames(String... filenames) {
+        def classpath = parseClasspathFile()
+        def libs = findEntries(classpath, "lib")
+        assert libs*. at path*.text().collect { new File(it).name } as Set == filenames as Set
+    }
+
+    protected EclipseWtpComponentFixture getWtpComponent() {
+        return new EclipseWtpComponentFixture(testDirectory)
+    }
+
+    protected EclipseWtpComponentFixture wtpComponent(String project) {
+        return new EclipseWtpComponentFixture(testDirectory.file(project))
+    }
+
+    protected EclipseClasspathFixture getClasspath() {
+        return new EclipseClasspathFixture(testDirectory, executer.gradleUserHomeDir)
+    }
+
+    protected EclipseClasspathFixture classpath(String path) {
+        return new EclipseClasspathFixture(testDirectory.file(path), executer.gradleUserHomeDir)
+    }
+}
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 bb89efb..c16076b 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
@@ -71,7 +71,7 @@ dependencies {
     @Test
     @Issue("GRADLE-1622")
     void classpathContainsEntriesForDependenciesThatOnlyDifferByClassifier() {
-        given:
+        //given:
         def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
         module.artifact(classifier: 'extra')
         module.artifact(classifier: 'tests')
@@ -81,7 +81,7 @@ dependencies {
         def testsJar = module.artifactFile(classifier: 'tests')
         def anotherJar = mavenRepo.module('coolGroup', 'another', '1.0').publish().artifactFile
 
-        when:
+        //when:
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
@@ -98,7 +98,7 @@ dependencies {
 }
 """
 
-        then:
+        //then:
         def libraries = classpath.libs
         assert libraries.size() == 4
         libraries[0].assertHasJar(baseJar)
@@ -108,6 +108,173 @@ dependencies {
     }
 
     @Test
+    void includesTransitiveRepoFileDependencies() {
+        //given
+        def someArtifactJar = mavenRepo.module('someGroup', 'someArtifact', '1.0').publish().artifactFile
+        def someOtherArtifactJar = mavenRepo.module('someGroup', 'someOtherArtifact', '1.0').publish().artifactFile
+
+        //when
+        runEclipseTask """include 'a', 'b', 'c'""", """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+configure(project(":a")){
+    dependencies {
+        compile 'someGroup:someOtherArtifact:1.0'
+
+        compile project(':b')
+    }
+}
+
+configure(project(":b")){
+    dependencies {
+        compile project(':c')
+    }
+}
+
+configure(project(":c")){
+    dependencies {
+        compile 'someGroup:someArtifact:1.0'
+    }
+}
+"""
+
+        def libs = classpath("a").libs
+        assert classpath("a").projects == ["/b", "/c"]
+        assert libs.size() == 2
+        libs[0].assertHasJar(someOtherArtifactJar)
+        libs[1].assertHasJar(someArtifactJar)
+    }
+
+    @Test
+    void transitiveProjectDependenciesMappedAsDirectDependencies() {
+        given:
+        runEclipseTask """include 'a', 'b', 'c'""", """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+configure(project(":a")){
+    dependencies {
+        compile project(':b')
+    }
+}
+
+configure(project(":b")){
+    dependencies {
+        compile project(':c')
+    }
+}
+
+"""
+
+        then:
+        def eclipseClasspath = classpath("a")
+        assert eclipseClasspath.projects == ['/b', '/c']
+    }
+
+    @Test
+    void transitiveFileDependenciesMappedAsDirectDependencies() {
+        runEclipseTask """include 'a', 'b', 'c'""", """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+configure(project(":a")){
+    dependencies {
+        compile files("bar.jar")
+        compile project(':b')
+    }
+}
+
+configure(project(":b")){
+    dependencies {
+        compile project(':c')
+        compile files("baz.jar")
+
+    }
+}
+
+configure(project(":c")){
+    dependencies {
+        compile files("foo.jar")
+    }
+}
+"""
+
+        def eclipseClasspath = classpath("a")
+        assert eclipseClasspath.projects == ['/b', '/c']
+        eclipseClasspath.libs[0].assertHasJar(file("a/bar.jar"))
+        eclipseClasspath.libs[1].assertHasJar(file("c/foo.jar"))
+        eclipseClasspath.libs[2].assertHasJar(file("b/baz.jar"))
+    }
+
+    @Test
+    void classpathContainsConflictResolvedDependencies() {
+        def someLib1Jar = mavenRepo.module('someGroup', 'someLib', '1.0').publish().artifactFile
+        def someLib2Jar = mavenRepo.module('someGroup', 'someLib', '2.0').publish().artifactFile
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << """ include 'a', 'b'"""
+        def buildFile = file("build.gradle")
+        buildFile << """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+configure(project(":a")){
+    dependencies {
+        compile ('someGroup:someLib:1.0'){
+            force = project.hasProperty("forceDeps")
+        }
+        compile project(':b')
+    }
+}
+
+configure(project(":b")){
+    dependencies {
+        compile 'someGroup:someLib:2.0'
+    }
+}
+"""
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks("eclipse").run()
+
+        def libs = classpath("a").libs
+        assert classpath("a").projects == ["/b"]
+        assert libs.size() == 1
+        libs[0].assertHasJar(someLib2Jar)
+
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withArgument("-PforceDeps=true").withTasks("eclipse").run()
+
+        libs = classpath("a").libs
+        assert classpath("a").projects == ["/b"]
+        assert libs.size() == 1
+        libs[0].assertHasJar(someLib1Jar)
+    }
+
+
+    @Test
     void substituesPathVariablesIntoLibraryPathsExceptForJavadoc() {
         //given
         def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
@@ -280,7 +447,7 @@ eclipse.classpath {
     void handlesPlusMinusConfigurationsForProjectDeps() {
         //when
         runEclipseTask "include 'foo', 'bar', 'unwanted'",
-                """
+            """
 allprojects {
   apply plugin: 'java'
   apply plugin: 'eclipse'
@@ -425,10 +592,10 @@ eclipse.classpath {
         getClasspathFile() << '''<?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry kind="lib" path="/some/path/someDependency.jar" exported="true"/>
-	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar" exported="true"/>
-	<classpathentry kind="src" path="/someProject" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="/some/path/someDependency.jar"/>
+	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar"/>
+	<classpathentry kind="src" path="/someProject"/>
 </classpath>
 '''
 
@@ -481,9 +648,9 @@ apply plugin: 'eclipse'
         classpath << '''<?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry kind="lib" path="/some/path/someDependency.jar" exported="true"/>
-	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="/some/path/someDependency.jar"/>
+	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar"/>
 </classpath>
 '''
 
@@ -604,10 +771,10 @@ task generateForTest << {}
     }
 
     @Test
-    @Issue("GRADLE-1613")
-    void shouldAllowSettingNonExportedConfigurations() {
+    void configuringNonExportedConfigurationsIsDeprecated() {
         //when
-        runEclipseTask """
+        executer.withDeprecationChecksDisabled()
+        def result = runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
@@ -632,9 +799,10 @@ eclipse {
         def libraries = classpath.libs
         assert libraries.size() == 2
         libraries[0].assertHasJar(file('compileDependency.jar'))
-        libraries[0].assertExported()
+        libraries[0].assertNotExported() // we changed the behaviour to default to false
         libraries[1].assertHasJar(file('providedDependency.jar'))
         libraries[1].assertNotExported()
+        result.output.contains("EclipseClasspath.noExportConfigurations has been deprecated and is scheduled to be removed in Gradle 3.0")
     }
 
     @Test
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseDependencySubstitutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseDependencySubstitutionIntegrationTest.groovy
index 264f617..f72d0b3 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseDependencySubstitutionIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseDependencySubstitutionIntegrationTest.groovy
@@ -16,7 +16,6 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -38,8 +37,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withModule("junit:junit") {
-            it.useTarget project(":project1")
+        resolutionStrategy.dependencySubstitution {
+            substitute module("junit:junit:4.7") with project(":project1")
         }
     }
 }
@@ -51,7 +50,6 @@ project(":project2") {
     }
 
     @Test
-    @Ignore("not supported in 2.4 - LD - 14/4/15")
     void "transitive external dependency substituted with project dependency"() {
         mavenRepo.module("org.gradle", "module1").dependsOn("module2").publish()
         mavenRepo.module("org.gradle", "module2").publish()
@@ -72,8 +70,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withModule("org.gradle:module2") {
-            it.useTarget project(":project1")
+        resolutionStrategy.dependencySubstitution {
+            substitute module("org.gradle:module2:1.0") with project(":project1")
         }
     }
 }
@@ -103,8 +101,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withProject(":project1") {
-            it.useTarget "junit:junit:4.7"
+        resolutionStrategy.dependencySubstitution {
+            substitute project(":project1") with module("junit:junit:4.7")
         }
     }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
index 420119f..ef77e30 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
@@ -14,13 +14,21 @@
  * limitations under the License.
  */
 package org.gradle.plugins.ide.eclipse
-
+import junit.framework.AssertionFailedError
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.custommonkey.xmlunit.XMLAssert
+import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.TextUtil
+import org.junit.ComparisonFailure
 import org.junit.Rule
 import org.junit.Test
 import spock.lang.Issue
 
+import java.util.regex.Pattern
+
 class EclipseIntegrationTest extends AbstractEclipseIntegrationTest {
     private static String nonAscii = "\\u7777\\u8888\\u9999"
 
@@ -29,22 +37,66 @@ class EclipseIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void canCreateAndDeleteMetaData() {
+        when:
         File buildFile = testFile("master/build.gradle")
-        usingBuildFile(buildFile).run()
+        usingBuildFile(buildFile).withTasks("eclipse").run()
+
+        assertHasExpectedContents(getClasspathFile(project:"api"), "apiClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"api"), "apiProject.xml")
+        assertHasExpectedContents(getComponentFile(project:"api"), "apiWtpComponent.xml")
+        assertHasExpectedContents(getFacetFile(project:"api"), "apiWtpFacet.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"api"), "apiJdt.properties")
+
+        assertHasExpectedContents(getClasspathFile(project:"common"), "commonClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"common"), "commonProject.xml")
+        assertHasExpectedContents(getComponentFile(project:"common"), "commonWtpComponent.xml")
+        assertHasExpectedContents(getFacetFile(project:"common"), "commonWtpFacet.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"common"), "commonJdt.properties")
+
+        assertHasExpectedContents(getClasspathFile(project:"groovyproject"), "groovyprojectClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"groovyproject"), "groovyprojectProject.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"groovyproject"), "groovyprojectJdt.properties")
+
+        assertHasExpectedContents(getClasspathFile(project:"javabaseproject"), "javabaseprojectClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"javabaseproject"), "javabaseprojectProject.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"javabaseproject"), "javabaseprojectJdt.properties")
+
+
+        assertHasExpectedContents(getProjectFile(project:"master"), "masterProject.xml")
+
+        assertHasExpectedContents(getClasspathFile(project:"webAppJava6"), "webAppJava6Classpath.xml")
+        assertHasExpectedContents(getProjectFile(project:"webAppJava6"), "webAppJava6Project.xml")
+        assertHasExpectedContents(getComponentFile(project:"webAppJava6"), "webAppJava6WtpComponent.xml")
+        assertHasExpectedContents(getFacetFile(project:"webAppJava6"), "webAppJava6WtpFacet.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"webAppJava6"), "webAppJava6Jdt.properties")
+
+        assertHasExpectedContents(getClasspathFile(project:"webAppWithVars"), "webAppWithVarsClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"webAppWithVars"), "webAppWithVarsProject.xml")
+        assertHasExpectedContents(getComponentFile(project:"webAppWithVars"), "webAppWithVarsWtpComponent.xml")
+        assertHasExpectedContents(getFacetFile(project:"webAppWithVars"), "webAppWithVarsWtpFacet.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"webAppWithVars"), "webAppWithVarsJdt.properties")
+
+        assertHasExpectedContents(getClasspathFile(project:"webservice"), "webserviceClasspath.xml")
+        assertHasExpectedContents(getProjectFile(project:"webservice"), "webserviceProject.xml")
+        assertHasExpectedContents(getComponentFile(project:"webservice"), "webserviceWtpComponent.xml")
+        assertHasExpectedContents(getFacetFile(project:"webservice"), "webserviceWtpFacet.xml")
+        assertHasExpectedProperties(getJdtPropertiesFile(project:"webservice"), "webserviceJdt.properties")
+
+        usingBuildFile(buildFile).withTasks("cleanEclipse").run()
     }
 
     @Test
     void sourceEntriesInClasspathFileAreSortedAsPerUsualConvention() {
         def expectedOrder = [
-                "src/main/java",
-                "src/main/groovy",
-                "src/main/resources",
-                "src/test/java",
-                "src/test/groovy",
-                "src/test/resources",
-                "src/integTest/java",
-                "src/integTest/groovy",
-                "src/integTest/resources"
+            "src/main/java",
+            "src/main/groovy",
+            "src/main/resources",
+            "src/test/java",
+            "src/test/groovy",
+            "src/test/resources",
+            "src/integTest/java",
+            "src/integTest/groovy",
+            "src/integTest/resources"
         ]
 
         expectedOrder.each { testFile(it).mkdirs() }
@@ -361,4 +413,46 @@ dependencies {
 }
         """
     }
+
+    void assertHasExpectedContents(TestFile actualFile, String expectedFileName) {
+        actualFile.assertExists()
+        TestFile expectedFile = testDirectory.file("expectedFiles/$expectedFileName").assertIsFile()
+        String expectedXml = expectedFile.text
+        String actualXml = getActualXml(actualFile)
+        Diff diff = new Diff(expectedXml, actualXml)
+
+        diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
+        try {
+            XMLAssert.assertXMLEqual(diff, true)
+        } catch (AssertionFailedError error) {
+            println "EXPECTED:\n${expectedXml}"
+            println "ACTUAL:\n${actualXml}"
+            throw new ComparisonFailure("Comparison filure: expected: $expectedFile, actual: $actualFile"
+                + "\nUnexpected content for generated actualFile: ${error.message}", expectedXml, actualXml).initCause(error)
+        }
+    }
+
+    void assertHasExpectedProperties(TestFile actualFile, String expectedFileName) {
+        actualFile.assertExists()
+        TestFile expectedFile = testDirectory.file("expectedFiles/$expectedFileName").assertIsFile()
+        Properties expected = new Properties()
+        expected.load(new ByteArrayInputStream(expectedFile.bytes))
+        Properties actual = new Properties()
+        actual.load(new ByteArrayInputStream(actualFile.bytes))
+        assert expected == actual
+    }
+
+    String getActualXml(File file) {
+        def gradleUserHomeDir = executer.getGradleUserHomeDir()
+        def homeDir = gradleUserHomeDir.absolutePath.replace(File.separator, '/')
+        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/${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
+        text = text.replaceAll("jar:file:/", 'jar:file:')
+        return text
+    }
+
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpWebAndJavaProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpWebAndJavaProjectIntegrationTest.groovy
index f34a574..3d9a41e 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpWebAndJavaProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpWebAndJavaProjectIntegrationTest.groovy
@@ -79,7 +79,7 @@ project(':java') {
         javaClasspath.lib('hamcrest-core-1.3.jar').assertIsExcludedFromDeployment()
 
         def webClasspath = classpath('web')
-        webClasspath.assertHasLibs('commons-lang3-3.0.jar', 'javax.servlet-api-3.1.0.jar', 'junit-4.12.jar', 'hamcrest-core-1.3.jar')
+        webClasspath.assertHasLibs('commons-lang3-3.0.jar', 'javax.servlet-api-3.1.0.jar', 'junit-4.12.jar', "guava-18.0.jar", 'hamcrest-core-1.3.jar')
         webClasspath.lib('commons-lang3-3.0.jar').assertIsExcludedFromDeployment()
         webClasspath.lib('javax.servlet-api-3.1.0.jar').assertIsExcludedFromDeployment()
         webClasspath.lib('junit-4.12.jar').assertIsExcludedFromDeployment()
@@ -106,7 +106,7 @@ project(':java') {
         webComponent.resources.size() == 2
         webComponent.sourceDirectory('src/main/java').assertDeployedAt('/WEB-INF/classes')
         webComponent.sourceDirectory('src/main/webapp').assertDeployedAt('/')
-        webComponent.modules.size() == 2
+        webComponent.modules.size() == 3
         webComponent.lib('commons-lang3-3.0.jar').assertDeployedAt('/WEB-INF/lib')
         webComponent.project('java').assertDeployedAt('/WEB-INF/lib')
     }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaDependencySubstitutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaDependencySubstitutionIntegrationTest.groovy
index 4937b10..5d2f9f9 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaDependencySubstitutionIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaDependencySubstitutionIntegrationTest.groovy
@@ -15,10 +15,8 @@
  */
 
 package org.gradle.plugins.ide.idea
-
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -40,8 +38,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withModule("junit:junit") {
-            it.useTarget project(":project1")
+        resolutionStrategy.dependencySubstitution {
+            substitute module("junit:junit:4.7") with project(":project1")
         }
     }
 }
@@ -54,7 +52,6 @@ project(":project2") {
     }
 
     @Test
-    @Ignore("not supported in 2.4 - LD - 14/4/15")
     void "transitive external dependency substituted with project dependency"() {
         mavenRepo.module("org.gradle", "module1").dependsOn("module2").publish()
         mavenRepo.module("org.gradle", "module2").publish()
@@ -75,8 +72,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withModule("org.gradle:module2") {
-            it.useTarget project(":project1")
+        resolutionStrategy.dependencySubstitution {
+            substitute module("org.gradle:module2:1.0") with project(":project1")
         }
     }
 }
@@ -107,8 +104,8 @@ project(":project2") {
     }
 
     configurations.all {
-        resolutionStrategy.dependencySubstitution.withProject(":project1") {
-            it.useTarget "junit:junit:4.7"
+        resolutionStrategy.dependencySubstitution {
+            substitute project(":project1") with module("junit:junit:4.7")
         }
     }
 }
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 4f7d20a..9288cd4 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
@@ -15,14 +15,15 @@
  */
 
 package org.gradle.plugins.ide.idea
-
+import junit.framework.AssertionFailedError
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.custommonkey.xmlunit.XMLAssert
 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.ComparisonFailure
 import org.junit.Rule
 import org.junit.Test
 
@@ -365,26 +366,24 @@ idea.project {
     }
 
     private void assertHasExpectedContents(String path) {
-        TestFile file = testDirectory.file(path).assertIsFile()
+        TestFile actualFile = testDirectory.file(path).assertIsFile()
         TestFile expectedFile = testDirectory.file("expectedFiles/${path}.xml").assertIsFile()
 
         def expectedXml = expectedFile.text
 
         def homeDir = executer.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
         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@/')
+        def actualXml = actualFile.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
 
         Diff diff = new Diff(expectedXml, actualXml)
         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
         try {
-            assert diff.similar()
-        } catch (AssertionError e) {
-            if (OperatingSystem.current().unix) {
-                def process = ["diff", expectedFile.absolutePath, file.absolutePath].execute()
-                process.consumeProcessOutput(System.out, System.err)
-                process.waitFor()
-            }
-            throw new AssertionError("generated file '$path' does not contain the expected contents: ${e.message}.\nExpected:\n${expectedXml}\nActual:\n${actualXml}").initCause(e)
+            XMLAssert.assertXMLEqual(diff, true)
+        } catch (AssertionFailedError error) {
+            println "EXPECTED:\n${expectedXml}"
+            println "ACTUAL:\n${actualXml}"
+            throw new ComparisonFailure("Comparison filure: expected: $expectedFile, actual: $actualFile"
+                + "\nUnexpected content for generated actualFile: ${error.message}", expectedXml, actualXml).initCause(error)
         }
     }
 
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 888057a..df4b88d 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
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 package org.gradle.plugins.ide.idea
-
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
@@ -253,16 +252,79 @@ project(':three') {
 
         //then
         def dependencies = parseIml("master/one/one.iml").dependencies
-        assert dependencies.modules.size() == 1
+        assert dependencies.modules.size() == 2
         dependencies.assertHasModule("COMPILE", "two")
+        dependencies.assertHasModule("COMPILE", "three")
 
         dependencies = parseIml("master/two/two.iml").dependencies
-        assert dependencies.modules.size() == 1
+        assert dependencies.modules.size() == 2
         dependencies.assertHasModule("COMPILE", "three")
+        dependencies.assertHasModule("COMPILE", "one")
 
         dependencies = parseIml("master/three/three.iml").dependencies
-        assert dependencies.modules.size() == 1
+        assert dependencies.modules.size() == 2
         dependencies.assertHasModule("COMPILE", "one")
+        dependencies.assertHasModule("COMPILE", "two")
+    }
+
+    @Test
+    void classpathContainsConflictResolvedDependencies() {
+        def someLib1Jar = mavenRepo.module('someGroup', 'someLib', '1.0').publish().artifactFile
+        def someLib2Jar= mavenRepo.module('someGroup', 'someLib', '2.0').publish().artifactFile
+
+        def settingsFile = file("master/settings.gradle")
+        settingsFile << """
+include 'one'
+include 'two'
+        """
+        def buildFile = file("master/build.gradle")
+        buildFile << """
+allprojects {
+    apply plugin: 'java'
+    apply plugin: 'idea'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+project(':one') {
+    dependencies {
+        compile ('someGroup:someLib:1.0') {
+            force = project.hasProperty("forceDeps")
+        }
+        compile project(':two')
+    }
+}
+
+project(':two') {
+    dependencies {
+        compile 'someGroup:someLib:2.0'
+    }
+}
+
+"""
+        //when
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks("idea").run()
+
+        //then
+        def dependencies = parseIml("master/one/one.iml").dependencies
+        dependencies.assertHasModule("COMPILE", "two")
+        assert dependencies.libraries*.jarName == [someLib2Jar.name]
+
+        dependencies = parseIml("master/two/two.iml").dependencies
+        assert dependencies.libraries*.jarName == [someLib2Jar.name]
+
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withArgument("-PforceDeps=true").withTasks("idea").run()
+
+        //then
+        dependencies = parseIml("master/one/one.iml").dependencies
+        assert dependencies.modules.size() == 1
+        dependencies.assertHasModule("COMPILE", "two")
+        assert dependencies.libraries*.jarName == [someLib1Jar.name]
+
+        dependencies = parseIml("master/two/two.iml").dependencies
+        assert dependencies.libraries*.jarName == [someLib2Jar.name]
     }
 
     @Test
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
index 7c10e39..910c5a7 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
@@ -1,5 +1,5 @@
-package org.gradle.integtests.IdeaIntegrationTest.api.src.main.java.org.gradle.api;
+package org.gradle.api;
 
 public class PersonList {
-   
+
 }
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 9f83cec..4df2c38 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
@@ -1,16 +1,15 @@
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="src" path="src/integTest/java"/>
-	<classpathentry kind="src" path="src/main/resources"/>
 	<classpathentry kind="src" path="src/main/java"/>
-	<classpathentry kind="src" path="src/test/resources"/>
+	<classpathentry kind="src" path="src/main/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/@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 kind="src" path="src/test/resources"/>
+	<classpathentry kind="src" path="src/integTest/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<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">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<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"/>
+	<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"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonClasspath.xml
index 71c32c9..ea1ec1e 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonClasspath.xml
@@ -1,28 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="src" path="src/main/resources"/>
 	<classpathentry kind="src" path="src/main/java"/>
+	<classpathentry kind="src" path="src/main/resources"/>
 	<classpathentry kind="src" path="src/test/java"/>
-	<classpathentry kind="src" path="/api" exported="true"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17.jar" exported="true">
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="src" path="/api"/>
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17.jar">
 		<attributes>
 			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5-sources.jar" kind="var"
-					path="GRADLE_USER_HOME/@CACHE@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5.jar" exported="true">
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5.jar">
 		<attributes>
 			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5-javadoc.jar!/"/>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/junit/junit/4.12/@SHA1@/junit-4.12-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.12/@SHA1@/junit-4.12.jar" exported="true">
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/junit/junit/4.12/@SHA1@/junit-4.12-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.12/@SHA1@/junit-4.12.jar">
 		<attributes>
 			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/junit/junit/4.12/@SHA1@/junit-4.12-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/org.hamcrest/hamcrest-core/1.3/@SHA1@/hamcrest-core-1.3-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/org.hamcrest/hamcrest-core/1.3/@SHA1@/hamcrest-core-1.3.jar" exported="true">
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/org.hamcrest/hamcrest-core/1.3/@SHA1@/hamcrest-core-1.3-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/org.hamcrest/hamcrest-core/1.3/@SHA1@/hamcrest-core-1.3.jar">
 		<attributes>
 			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/org.hamcrest/hamcrest-core/1.3/@SHA1@/hamcrest-core-1.3-javadoc.jar!/"/>
 		</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 d4e5d41..8daa3df 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
@@ -1,12 +1,11 @@
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="src" path="src/main/resources"/>
 	<classpathentry kind="src" path="src/main/java"/>
 	<classpathentry kind="src" path="src/main/groovy"/>
-	<classpathentry kind="src" path="src/test/resources"/>
+	<classpathentry kind="src" path="src/main/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/@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"/>
+	<classpathentry kind="src" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<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"/>
+	<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"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml
index 5a2ec8e..3408217 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml
@@ -1,4 +1,4 @@
 <classpath>
 	<classpathentry kind="output" path="bin"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
index 99535c7..32d6648 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
@@ -1,6 +1,19 @@
 <classpath>
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry kind="src" path="src/main/java"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
+	<classpathentry kind="src" path="/common"/>
+	<classpathentry kind="src" path="/api"/>
+	<classpathentry sourcepath="@CACHE_DIR@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17-sources.jar" kind="lib" path="@CACHE_DIR@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17-javadoc.jar!/"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry sourcepath="@CACHE_DIR@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5-sources.jar" kind="lib" path="@CACHE_DIR@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5-javadoc.jar!/"/>
+		</attributes>
+	</classpathentry>
+	<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"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml
index a093617..8bef658 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml
@@ -1,7 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <project-modules id="moduleCoreId" project-version="2.0">
 	<wb-module deploy-name="webAppJava6">
 		<property name="context-root" value="webAppJava6"/>
 		<wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
 		<wb-resource deploy-path="/" source-path="src/main/webapp"/>
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/common/common">
+			<dependency-type>uses</dependency-type>
+		</dependent-module>
+		<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@/log4j/log4j/1.2.17/@SHA1@/log4j-1.2.17.jar">
+			<dependency-type>uses</dependency-type>
+		</dependent-module>
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/joda-time/joda-time/2.5/@SHA1@/joda-time-2.5.jar">
+			<dependency-type>uses</dependency-type>
+		</dependent-module>
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar">
+			<dependency-type>uses</dependency-type>
+		</dependent-module>
 	</wb-module>
 </project-modules>
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 63eb179..0d7decd 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
@@ -1,13 +1,13 @@
 <classpath>
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry kind="src" path="src/main/java"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
 	<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">
+					path="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5.jar">
 		<attributes>
 			<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/@SHA1@/junit-4.7-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/@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"/>
 </classpath>
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 60f544f..bed8b27 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
@@ -1,20 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry kind="src" path="src/main/java"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
-	<classpathentry kind="src" path="/api" exported="true"/>
-	<classpathentry sourcepath="@CACHE_DIR@/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">
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
+	<classpathentry kind="src" path="/api"/>
+	<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"/>
+	<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">
 		<attributes>
-			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/@SHA1@/commons-lang-2.5-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@/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">
+	<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">
 		<attributes>
-			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2-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@/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"/>
+	<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"/>
+	<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"/>
 </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 e60b197..53d8ece 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
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <project-modules id="moduleCoreId" project-version="2.0">
 	<wb-module deploy-name="webservice">
 		<property name="context-root" value="webservice"/>
@@ -8,5 +9,8 @@
 		<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>
+		<dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar">
+			<dependency-type>uses</dependency-type>
+		</dependent-module>
 	</wb-module>
 </project-modules>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java
index 7c10e39..910c5a7 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java
@@ -1,5 +1,5 @@
-package org.gradle.integtests.IdeaIntegrationTest.api.src.main.java.org.gradle.api;
+package org.gradle.api;
 
 public class PersonList {
-   
+
 }
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 5b1e6e7..416bb8d 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,23 +1,4 @@
-import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
-
-import java.util.regex.Pattern
-import junit.framework.AssertionFailedError
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
-import org.custommonkey.xmlunit.XMLAssert
 import org.gradle.plugins.ide.eclipse.model.AbstractClasspathEntry
-import org.junit.ComparisonFailure
-
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath 'xmlunit:xmlunit:1.3', 'junit:junit:4.8.1'
-    }
-}
-
-defaultTasks 'eclipse', 'cleanEclipse'
 
 allprojects {
     apply plugin: 'eclipse'
@@ -45,11 +26,6 @@ configure(subprojects.findAll{ it.path in [ ':api', ':common', ':webAppJava6', '
 allprojects {
     afterEvaluate { p ->
         configure(p) {
-            eclipseProject.doLast {
-                compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Project.xml"),
-                        file(".project"))
-            }
-
             if (p.hasProperty('eclipseClasspath')) {
                 eclipse {
                     classpath {
@@ -65,38 +41,6 @@ allprojects {
                         }
                     }
                 }
-                eclipseClasspath {
-                    doLast {
-                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Classpath.xml"),
-                                file(".classpath"))
-                    }
-                }
-            }
-
-            if (p.hasProperty('eclipseJdt')) {
-                eclipseJdt {
-                    doLast {
-                        compareProperties(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Jdt.properties")),
-                                getActualXml(file(".settings/org.eclipse.jdt.core.prefs")))
-                    }
-                }
-            }
-
-            if (p.hasProperty('eclipseWtpComponent')) {
-                eclipseWtpComponent {
-                    doLast {
-                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}WtpComponent.xml"),
-                                file(".settings/org.eclipse.wst.common.component"))
-                    }
-                }
-            }
-            if (p.hasProperty('eclipseWtpFacet')) {
-                eclipseWtpFacet {
-                    doLast {
-                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}WtpFacet.xml"),
-                                file(".settings/org.eclipse.wst.common.project.facet.core.xml"))
-                    }
-                }
             }
             cleanEclipse.doLast {
                 assert !file(".classpath").exists()
@@ -106,43 +50,3 @@ allprojects {
         }
     }
 }
-
-void compareProperties(String expectedProperties, String actualProperties) {
-    Properties expected = new Properties()
-    expected.load(new ByteArrayInputStream(expectedProperties.bytes))
-    Properties actual = new Properties()
-    actual.load(new ByteArrayInputStream(actualProperties.bytes))
-    assert expected == actual
-}
-
-void compareXmlWithIgnoringOrder(File expectedFile, File actualFile) {
-    String expectedXml = getExpectedXml(expectedFile)
-    String actualXml = getActualXml(actualFile)
-    Diff diff = new Diff(expectedXml, actualXml)
-    diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
-    try {
-        XMLAssert.assertXMLEqual(diff, true)
-    } catch (AssertionFailedError error) {
-        println "EXPECTED:\n${expectedXml}"
-        println "ACTUAL:\n${actualXml}"
-        throw new ComparisonFailure("Comparison filure: expected: $expectedFile, actual: $actualFile"
-            + "\nUnexpected content for generated file: ${error.message}", expectedXml, actualXml).initCause(error)
-    }
-}
-
-String getExpectedXml(File file) {
-    return file.text
-}
-
-String getActualXml(File file) {
-    def homeDir = gradle.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
-    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/${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
-    text = text.replaceAll("jar:file:/", 'jar:file:')
-    return text
-}
-
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle
index f898d2a..5110c38 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle
@@ -1,3 +1,7 @@
 apply plugin: 'war'
 
 sourceCompatibility = 6
+
+dependencies {
+    compile project(":common")
+}
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
index f7ce8c2..be03957 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
@@ -1,5 +1,5 @@
-package org.gradle.integtests.IdeaIntegrationTest.webservice.src.main.java.org.gradle.webservice;
+package org.gradle.webservice;
 
 public class TestTest {
-    
+
 }
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 c094665..9a8eb48 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
@@ -9,7 +9,7 @@
       <excludeFolder url="file://$MODULE_DIR$/build"/>
     </content>
     <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="" scope="RUNTIME">
+    <orderEntry type="module-library" scope="RUNTIME">
       <library>
         <CLASSES>
           <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar!/"/>
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 e9f0b81..8bcae09 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
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <module relativePaths="true" type="JAVA_MODULE" version="4">
   <component name="NewModuleRootManager" inherit-compiler-output="true">
     <exclude-output/>
@@ -19,7 +20,19 @@
         </SOURCES>
       </library>
     </orderEntry>
-    <orderEntry type="module-library" exported="">
+    <orderEntry type="module" module-name="api"/>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <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/@SHA1@/commons-collections-3.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
       <library>
         <CLASSES>
           <root url="jar://$MODULE_DIR$/lib/compile-1.0.jar!/"/>
@@ -28,8 +41,7 @@
         <SOURCES/>
       </library>
     </orderEntry>
-    <orderEntry type="module" module-name="api" exported=""/>
-    <orderEntry type="module-library" exported="" scope="RUNTIME">
+    <orderEntry type="module-library" scope="RUNTIME">
       <library>
         <CLASSES>
           <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/@SHA1@/commons-lang-2.4.jar!/"/>
@@ -42,7 +54,7 @@
         </SOURCES>
       </library>
     </orderEntry>
-    <orderEntry type="module-library" exported="" scope="RUNTIME">
+    <orderEntry type="module-library" scope="RUNTIME">
       <library>
         <CLASSES>
           <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/@SHA1@/commons-io-1.2.jar!/"/>
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 3012f22..7f66bb5 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
@@ -7,7 +7,7 @@
     </content>
     <orderEntry type="inheritedJdk"/>
     <orderEntry type="sourceFolder" forTests="false"/>
-    <orderEntry type="module-library" exported="" scope="RUNTIME">
+    <orderEntry type="module-library" scope="RUNTIME">
       <library>
         <CLASSES>
           <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/@SHA1@/commons-collections-3.2.jar!/"/>
@@ -18,7 +18,7 @@
         </SOURCES>
       </library>
     </orderEntry>
-    <orderEntry type="module-library" exported="" scope="RUNTIME">
+    <orderEntry type="module-library" scope="RUNTIME">
       <library>
         <CLASSES>
           <root url="jar://@CACHE_DIR@/junit/junit/4.7/@SHA1@/junit-4.7.jar!/"/>
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 f19fad5..33a0a97 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
@@ -18,12 +18,18 @@ package org.gradle.plugins.ide.eclipse.model
 import org.gradle.api.Nullable
 import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.util.DeprecationLogger
 
 abstract class AbstractLibrary extends AbstractClasspathEntry {
+    private static final String DEPRECATED_DECLAREDCONFIGNAME_FIELD = "AbstractLibrary.declaredConfigurationName"
+
     FileReference sourcePath
     FileReference javadocPath
     FileReference library
+
+    @Deprecated
     String declaredConfigurationName
+
     @Nullable
     ModuleVersionIdentifier moduleVersion
 
@@ -42,6 +48,16 @@ abstract class AbstractLibrary extends AbstractClasspathEntry {
         path = library.path
     }
 
+    void setDeclaredConfigurationName(String declaredConfigurationName) {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_DECLAREDCONFIGNAME_FIELD)
+        this.declaredConfigurationName = declaredConfigurationName
+    }
+
+    String getDeclaredConfigurationName() {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_DECLAREDCONFIGNAME_FIELD)
+        return declaredConfigurationName
+    }
+
     void setJavadocPath(FileReference path) {
         this.javadocPath = path
         entryAttributes.javadoc_location = path ? path.jarURL : null
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 4d011fd..1a350e0 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
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 package org.gradle.plugins.ide.eclipse.model
-
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.tasks.SourceSet
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.eclipse.model.internal.ClasspathFactory
-import org.gradle.plugins.ide.eclipse.model.internal.ExportedEntriesUpdater
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 import org.gradle.util.ConfigureUtil
+import org.gradle.util.DeprecationLogger
 
 /**
  * The build path settings for the generated Eclipse project. Used by the
@@ -51,9 +50,6 @@ import org.gradle.util.ConfigureUtil
  *     //you can also remove configurations from the classpath:
  *     minusConfigurations += [ configurations.someBoringConfig ]
  *
- *     //if you don't want some classpath entries 'exported' in Eclipse
- *     noExportConfigurations += [ configurations.provided ]
- *
  *     //if you want to append extra containers:
  *     containers 'someFriendlyContainer', 'andYetAnotherContainer'
  *
@@ -105,6 +101,8 @@ import org.gradle.util.ConfigureUtil
  * </pre>
  */
 class EclipseClasspath {
+    private static final String DEPRECATED_NOEXPORTCONFIGURATION_FIELD = "EclipseClasspath.noExportConfigurations"
+
 
     /**
      * The source sets to be added.
@@ -132,6 +130,8 @@ class EclipseClasspath {
      * <p>
      * See {@link EclipseClasspath} for an example.
      */
+
+    @Deprecated
     Collection<Configuration> noExportConfigurations = []
 
     /**
@@ -206,9 +206,7 @@ class EclipseClasspath {
      * Calculates, resolves and returns dependency entries of this classpath.
      */
     public List<ClasspathEntry> resolveDependencies() {
-        def entries = new ClasspathFactory().createEntries(this)
-        new ExportedEntriesUpdater().updateExported(entries, this.noExportConfigurations*.name)
-        return entries
+        return new ClasspathFactory().createEntries(this)
     }
 
     void mergeXmlClasspath(Classpath xmlClasspath) {
@@ -223,4 +221,14 @@ class EclipseClasspath {
         pathVariables.each { name, dir -> referenceFactory.addPathVariable(name, dir) }
         return referenceFactory
     }
+
+    Collection<Configuration> getNoExportConfigurations() {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_NOEXPORTCONFIGURATION_FIELD)
+        return noExportConfigurations
+    }
+
+    void setNoExportConfigurations(Collection<Configuration> noExportConfigurations) {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_NOEXPORTCONFIGURATION_FIELD)
+        this.noExportConfigurations = noExportConfigurations
+    }
 }
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 a00df7c..8499eb3 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,15 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.util.DeprecationLogger
+
 class ProjectDependency extends AbstractClasspathEntry {
 
+    private static final String DEPRECATED_DECLAREDCONFIGNAME_FIELD = "ProjectDependency.declaredConfigurationName"
+
     String gradlePath
+
+    @Deprecated
     String declaredConfigurationName
 
     ProjectDependency(Node node) {
@@ -31,6 +37,16 @@ class ProjectDependency extends AbstractClasspathEntry {
         this.gradlePath = gradlePath
     }
 
+    String getDeclaredConfigurationName() {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_DECLAREDCONFIGNAME_FIELD)
+        return declaredConfigurationName
+    }
+
+    void setDeclaredConfigurationName(String declaredConfigurationName) {
+        DeprecationLogger.nagUserOfDeprecated(DEPRECATED_DECLAREDCONFIGNAME_FIELD)
+        this.declaredConfigurationName = declaredConfigurationName
+    }
+
     private void assertPathIsValid() {
         assert path.startsWith('/')
     }
@@ -42,4 +58,4 @@ class ProjectDependency extends AbstractClasspathEntry {
     public String toString() {
         return "ProjectDependency{" + super.toString()
     }
-}
\ No newline at end of file
+}
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 19740b3..2215ba9 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
@@ -21,6 +21,7 @@ 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
+import org.gradle.util.DeprecationLogger
 
 class ClasspathFactory {
 
@@ -34,7 +35,6 @@ class ClasspathFactory {
         void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath) {
             eclipseClasspath.containers.each { container ->
                 Container entry = new Container(container)
-                entry.exported = true
                 entries << entry
             }
         }
@@ -95,8 +95,13 @@ class ClasspathFactory {
 
         out.javadocPath = javadocRef
         out.sourcePath = sourceRef
-        out.exported = true
-        out.declaredConfigurationName = declaredConfigurationName
+        out.exported = false
+        DeprecationLogger.whileDisabled(new Runnable() {
+            @Override
+            void run() {
+                out.declaredConfigurationName = declaredConfigurationName
+            }
+        })
         out.moduleVersion = id
         out
     }
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
deleted file mode 100644
index 465200d..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.plugins.ide.eclipse.model.internal
-
-import org.gradle.plugins.ide.eclipse.model.AbstractLibrary
-import org.gradle.plugins.ide.eclipse.model.ClasspathEntry
-import org.gradle.plugins.ide.eclipse.model.ProjectDependency
-
-class ExportedEntriesUpdater {
-    void updateExported(List<ClasspathEntry> classpathEntries, List<String> noExportConfigNames) {
-        classpathEntries.each {
-            if (it instanceof AbstractLibrary || it instanceof ProjectDependency) {
-                if (noExportConfigNames.contains(it.declaredConfigurationName)) {
-                    it.exported = false
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
index cb66486..39c0e41 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
@@ -18,6 +18,7 @@ package org.gradle.plugins.ide.eclipse.model.internal
 import org.gradle.api.Project
 import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.eclipse.model.ProjectDependency
+import org.gradle.util.DeprecationLogger
 
 class ProjectDependencyBuilder {
     ProjectDependency build(Project project, String declaredConfigurationName) {
@@ -28,8 +29,13 @@ class ProjectDependencyBuilder {
             name = project.name
         }
         def out = new ProjectDependency('/' + name, project.path)
-        out.exported = true
-        out.declaredConfigurationName = declaredConfigurationName
+        out.exported = false
+        DeprecationLogger.whileDisabled(new Runnable() {
+            @Override
+            void run() {
+                out.declaredConfigurationName = declaredConfigurationName
+            }
+        })
         out
     }
 }
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 6ce9fe0..0e34b61 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
@@ -34,7 +34,7 @@ class ModuleDependency implements Dependency {
     def ModuleDependency(name, scope) {
         this.name = name;
         this.scope = scope;
-        this.exported = !scope || scope == 'COMPILE' || scope == 'RUNTIME'
+        this.exported = false
     }
 
     void addToNode(Node parentNode) {
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 d7fe163..6af260e 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
@@ -55,7 +55,7 @@ class ModuleLibrary implements Dependency {
         this.javadoc = javadoc as LinkedHashSet;
         this.sources = sources as LinkedHashSet;
         this.scope = scope
-        this.exported = !scope || scope == 'COMPILE' || scope == 'RUNTIME'
+        this.exported = false
     }
 
     void addToNode(Node parentNode) {
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
index 9538588..001d1af 100644
--- 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
@@ -26,9 +26,9 @@ import org.gradle.api.Task;
 import org.gradle.api.internal.project.ProjectTaskLister;
 import org.gradle.api.internal.tasks.PublicTaskSpecification;
 import org.gradle.tooling.internal.consumer.converters.TaskNameComparator;
-import org.gradle.tooling.internal.impl.DefaultBuildInvocations;
-import org.gradle.tooling.internal.impl.LaunchableGradleTask;
-import org.gradle.tooling.internal.impl.LaunchableGradleTaskSelector;
+import org.gradle.plugins.ide.internal.tooling.model.DefaultBuildInvocations;
+import org.gradle.plugins.ide.internal.tooling.model.LaunchableGradleTask;
+import org.gradle.plugins.ide.internal.tooling.model.LaunchableGradleTaskSelector;
 import org.gradle.tooling.model.internal.ProjectSensitiveToolingModelBuilder;
 
 import java.util.Collection;
@@ -36,6 +36,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.gradle.plugins.ide.internal.tooling.ToolingModelBuilderSupport.buildFromTask;
+
 public class BuildInvocationsBuilder extends ProjectSensitiveToolingModelBuilder {
 
     private final ProjectTaskLister taskLister;
@@ -86,12 +88,7 @@ public class BuildInvocationsBuilder extends ProjectSensitiveToolingModelBuilder
     private List<LaunchableGradleTask> tasks(Project project) {
         List<LaunchableGradleTask> tasks = Lists.newArrayList();
         for (Task task : taskLister.listProjectTasks(project)) {
-            tasks.add(new LaunchableGradleTask()
-                    .setPath(task.getPath())
-                    .setName(task.getName())
-                    .setDisplayName(task.toString())
-                    .setDescription(task.getDescription())
-                    .setPublic(PublicTaskSpecification.INSTANCE.isSatisfiedBy(task)));
+            tasks.add(buildFromTask(new LaunchableGradleTask(), task));
         }
         return tasks;
     }
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
index aec1918..f6f81d8 100644
--- 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
@@ -96,11 +96,11 @@ public class EclipseModelBuilder implements ToolingModelBuilder {
                 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()));
+                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source, library.getModuleVersion(), library.isExported()));
             } 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())));
+                projectDependencies.add(new DefaultEclipseProjectDependency(path, projectMapping.get(projectDependency.getGradlePath()), projectDependency.isExported()));
             } else if (entry instanceof SourceFolder) {
                 String path = ((SourceFolder) entry).getPath();
                 sourceDirectories.add(new DefaultEclipseSourceDirectory(path, project.file(path)));
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
index 16ba9e9..3905481 100644
--- 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
@@ -18,17 +18,18 @@ package org.gradle.plugins.ide.internal.tooling;
 
 import org.gradle.api.Project;
 import org.gradle.api.Task;
-import org.gradle.api.internal.tasks.PublicTaskSpecification;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.tooling.internal.gradle.DefaultGradleProject;
-import org.gradle.tooling.internal.impl.LaunchableGradleProjectTask;
-import org.gradle.tooling.internal.impl.LaunchableGradleTask;
+import org.gradle.plugins.ide.internal.tooling.model.LaunchableGradleProjectTask;
+import org.gradle.plugins.ide.internal.tooling.model.LaunchableGradleTask;
 import org.gradle.tooling.provider.model.ToolingModelBuilder;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.SortedSet;
 
+import static org.gradle.plugins.ide.internal.tooling.ToolingModelBuilderSupport.buildFromTask;
+
 /**
  * Builds the GradleProject that contains the project hierarchy and task information
  */
@@ -77,14 +78,7 @@ public class GradleProjectBuilder implements ToolingModelBuilder {
         for (String taskName : taskNames) {
             Task t = tasks.findByName(taskName);
             if (t != null) {
-                out.add(new LaunchableGradleProjectTask()
-                                .setProject(owner)
-                                .setPath(t.getPath())
-                                .setName(t.getName())
-                                .setDisplayName(t.toString())
-                                .setDescription(t.getDescription())
-                                .setPublic(PublicTaskSpecification.INSTANCE.isSatisfiedBy(t))
-                );
+                out.add(buildFromTask(new LaunchableGradleProjectTask(), t).setProject(owner));
             }
         }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingModelBuilderSupport.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingModelBuilderSupport.java
new file mode 100644
index 0000000..82764cf
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/tooling/ToolingModelBuilderSupport.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Task;
+import org.gradle.api.internal.tasks.PublicTaskSpecification;
+import org.gradle.plugins.ide.internal.tooling.model.LaunchableGradleTask;
+
+abstract class ToolingModelBuilderSupport {
+    public static <T extends LaunchableGradleTask> T buildFromTask(T target, Task task) {
+        target.setPath(task.getPath())
+                .setName(task.getName())
+                .setGroup(task.getGroup())
+                .setDisplayName(task.toString())
+                .setDescription(task.getDescription())
+                .setPublic(PublicTaskSpecification.INSTANCE.isSatisfiedBy(task));
+        return target;
+    }
+}
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
index d12c39c..fc2b31a 100644
--- 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
@@ -27,12 +27,15 @@ public class DefaultEclipseExternalDependency implements ExternalDependencyVersi
     private final File file;
     private final File javadoc;
     private final File source;
+
+    private final boolean exported;
     private final GradleModuleVersion moduleVersion;
 
-    public DefaultEclipseExternalDependency(File file, File javadoc, File source, ModuleVersionIdentifier identifier) {
+    public DefaultEclipseExternalDependency(File file, File javadoc, File source, ModuleVersionIdentifier identifier, boolean exported) {
         this.file = file;
         this.javadoc = javadoc;
         this.source = source;
+        this.exported = exported;
         moduleVersion = (identifier == null)? null : new DefaultGradleModuleVersion(identifier);
     }
 
@@ -51,4 +54,8 @@ public class DefaultEclipseExternalDependency implements ExternalDependencyVersi
     public GradleModuleVersion getGradleModuleVersion() {
         return moduleVersion;
     }
+
+    public boolean isExported() {
+        return exported;
+    }
 }
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
index 73765b9..82c09e5 100644
--- 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
@@ -19,11 +19,14 @@ import java.io.Serializable;
 
 public class DefaultEclipseProjectDependency implements Serializable {
     private final String path;
+
+    private final boolean exported;
     private final DefaultEclipseProject target;
 
-    public DefaultEclipseProjectDependency(String path, DefaultEclipseProject target) {
+    public DefaultEclipseProjectDependency(String path, DefaultEclipseProject target, boolean exported) {
         this.target = target;
         this.path = path;
+        this.exported = exported;
     }
 
     public DefaultEclipseProject getTargetProject() {
@@ -34,6 +37,10 @@ public class DefaultEclipseProjectDependency implements Serializable {
         return path;
     }
 
+    public boolean isExported() {
+        return exported;
+    }
+
     @Override
     public String toString() {
         return String.format("project dependency %s (%s)", path, target);
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
index 6dfd299..212c376 100644
--- 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
@@ -18,16 +18,20 @@ 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.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.artifacts.result.ResolvedComponentResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 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 org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -45,37 +49,22 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
      */
     public List<IdeProjectDependency> getIdeProjectDependencies(Configuration configuration, Project project) {
         ResolutionResult result = getIncomingResolutionResult(configuration);
-        List<ResolvedComponentResult> projectComponents = findAllResolvedDependencyResults(result.getRoot().getDependencies(), ProjectComponentIdentifier.class);
-
+        final Set<ResolvedComponentResult> projectComponents = CollectionUtils.filter(result.getAllComponents(), new Spec<ResolvedComponentResult>() {
+            @Override
+            public boolean isSatisfiedBy(ResolvedComponentResult element) {
+                return element.getId() instanceof ProjectComponentIdentifier;
+            }
+        });
         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());
-                }
+            if(!resolvedProject.equals(project)) {
+                ideProjectDependencies.add(new IdeProjectDependency(configuration, resolvedProject));
             }
         }
 
-        return matches;
+        return ideProjectDependencies;
     }
 
     /**
@@ -107,20 +96,22 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
     }
 
     /**
-     * Gets IDE local file dependencies.
+     * Gets IDE repository file dependencies.
      *
      * @param configuration Configuration
-     * @return IDE local file dependencies
+     * @return IDE repository file dependencies
      */
     public List<IdeExtendedRepoFileDependency> getIdeRepoFileDependencies(Configuration configuration) {
         ResolutionResult result = getIncomingResolutionResult(configuration);
-        List<ResolvedComponentResult> resolvedDependencies = new ArrayList<ResolvedComponentResult>();
-        findAllResolvedDependencyResultsAndTheirDependencies(resolvedDependencies, result.getRoot().getDependencies(), ModuleComponentIdentifier.class);
-        Set<ModuleVersionIdentifier> mappedResolvedDependencies = mapResolvedDependencies(resolvedDependencies);
+        final Set<ResolvedComponentResult> resolvedRepoFileComponents = CollectionUtils.filter(result.getAllComponents(), new Spec<ResolvedComponentResult>() {
+            @Override
+            public boolean isSatisfiedBy(ResolvedComponentResult element) {
+                return element.getId() instanceof ModuleComponentIdentifier;
+            }
+        });
+        Set<ModuleVersionIdentifier> mappedResolvedDependencies = mapResolvedDependencies(resolvedRepoFileComponents);
         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());
@@ -133,34 +124,12 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
     }
 
     /**
-     * Finds all resolved components of the given type from the given set of dependency edges. If resolved component has dependencies itself, recursively resolve them as well
-     * and add them to the results. This method can handle circular dependencies.
-     *
-     * @param dependencies Dependencies
-     * @return Resolved dependency results
-     */
-    private void findAllResolvedDependencyResultsAndTheirDependencies(List<ResolvedComponentResult> matches, Set<? extends DependencyResult> dependencies, Class<? extends ComponentIdentifier> type) {
-        for (DependencyResult dependencyResult : dependencies) {
-            if (dependencyResult instanceof ResolvedDependencyResult) {
-                ResolvedDependencyResult resolvedResult = (ResolvedDependencyResult) dependencyResult;
-                if (type.isInstance(resolvedResult.getSelected().getId())) {
-                    // avoid circular dependencies by checking whether component result is already added
-                    if (!matches.contains(resolvedResult.getSelected())) {
-                        matches.add(resolvedResult.getSelected());
-                        findAllResolvedDependencyResultsAndTheirDependencies(matches, resolvedResult.getSelected().getDependencies(), type);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
      * Maps resolved dependencies by module version identifier.
      *
      * @param components Resolved dependencies
      * @return Mapped, resolved dependencies
      */
-    private Set<ModuleVersionIdentifier> mapResolvedDependencies(List<ResolvedComponentResult> components) {
+    private Set<ModuleVersionIdentifier> mapResolvedDependencies(Set<ResolvedComponentResult> components) {
         Set<ModuleVersionIdentifier> mappedResolvedDependencies = new LinkedHashSet<ModuleVersionIdentifier>();
         for (ResolvedComponentResult component : components) {
             mappedResolvedDependencies.add(component.getModuleVersion());
@@ -175,7 +144,8 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
      * @return IDE local file dependencies
      */
     public List<IdeLocalFileDependency> getIdeLocalFileDependencies(Configuration configuration) {
-        List<SelfResolvingDependency> externalDependencies = findAllExternalDependencies(configuration);
+        List<SelfResolvingDependency> externalDependencies = new ArrayList<SelfResolvingDependency>();
+        findAllExternalDependencies(externalDependencies, new ArrayList<Dependency>(), configuration);
         List<IdeLocalFileDependency> ideLocalFileDependencies = new ArrayList<IdeLocalFileDependency>();
 
         for (SelfResolvingDependency externalDependency : externalDependencies) {
@@ -196,15 +166,17 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
      * @param configuration Configuration
      * @return External dependencies
      */
-    private List<SelfResolvingDependency> findAllExternalDependencies(Configuration configuration) {
-        List<SelfResolvingDependency> externalDependencies = new ArrayList<SelfResolvingDependency>();
-
+    private List<SelfResolvingDependency> findAllExternalDependencies(List<SelfResolvingDependency> externalDependencies, List<Dependency> visited, Configuration configuration) {
         for (Dependency dependency : configuration.getAllDependencies()) {
-            if (dependency instanceof SelfResolvingDependency && !(dependency instanceof ProjectDependency)) {
-                externalDependencies.add((SelfResolvingDependency) dependency);
+            if(!visited.contains(dependency)){
+                visited.add(dependency);
+                if(dependency instanceof ProjectDependency) {
+                    findAllExternalDependencies(externalDependencies, visited, ((ProjectDependency) dependency).getProjectConfiguration());
+                } else if (dependency instanceof SelfResolvingDependency) {
+                    externalDependencies.add((SelfResolvingDependency) dependency);
+                }
             }
         }
-
         return externalDependencies;
     }
 
@@ -263,4 +235,4 @@ public class DefaultIdeDependencyResolver implements IdeDependencyResolver {
     private Set<ResolvedArtifact> getExternalArtifacts(Configuration configuration) {
         return configuration.getResolvedConfiguration().getLenientConfiguration().getArtifacts(Specs.SATISFIES_ALL);
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/DefaultBuildInvocations.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/DefaultBuildInvocations.java
new file mode 100644
index 0000000..b489bf2
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/DefaultBuildInvocations.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.plugins.ide.internal.tooling.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Implementation of {@link org.gradle.tooling.model.gradle.BuildInvocations}
+ */
+public class DefaultBuildInvocations implements Serializable {
+    private List<? extends LaunchableGradleTaskSelector> selectors;
+    private List<? extends LaunchableGradleTask> tasks;
+
+    public DefaultBuildInvocations setSelectors(List<? extends LaunchableGradleTaskSelector> selectors) {
+        this.selectors = selectors;
+        return this;
+    }
+
+    public List<? extends LaunchableGradleTaskSelector> getTaskSelectors() {
+        return selectors;
+    }
+
+    public DefaultBuildInvocations setTasks(List<? extends LaunchableGradleTask> tasks) {
+        this.tasks = tasks;
+        return this;
+    }
+
+    public List<? extends LaunchableGradleTask> getTasks() {
+        return tasks;
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleProjectTask.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleProjectTask.java
new file mode 100644
index 0000000..9bbd39c
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/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.plugins.ide.internal.tooling.model;
+
+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/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTask.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTask.java
new file mode 100644
index 0000000..2e05bd3
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTask.java
@@ -0,0 +1,101 @@
+/*
+ * 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.model;
+
+import org.gradle.TaskExecutionRequest;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class LaunchableGradleTask implements Serializable, InternalLaunchable, TaskExecutionRequest {
+
+    private String path;
+    private String name;
+    private String description;
+    private String displayName;
+    private String group;
+    private boolean isPublic;
+
+    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 List<String> getArgs() {
+        return Collections.singletonList(path);
+    }
+
+    public String getProjectPath() {
+        return null;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public LaunchableGradleTask setGroup(String group) {
+        this.group = group;
+        return this;
+    }
+
+    public boolean isPublic() {
+        return isPublic;
+    }
+
+    public LaunchableGradleTask setPublic(boolean isPublic) {
+        this.isPublic = isPublic;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{path='" + path + "',public=" + isPublic + "}";
+    }
+}
diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTaskSelector.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTaskSelector.java
new file mode 100644
index 0000000..3fdb3cf
--- /dev/null
+++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/model/LaunchableGradleTaskSelector.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.plugins.ide.internal.tooling.model;
+
+import org.gradle.TaskExecutionRequest;
+import org.gradle.api.Nullable;
+import org.gradle.tooling.internal.protocol.InternalLaunchable;
+import org.gradle.tooling.model.TaskSelector;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Data used for {@link org.gradle.tooling.model.TaskSelector}.
+ */
+public class LaunchableGradleTaskSelector implements TaskSelector, InternalLaunchable, TaskExecutionRequest, Serializable {
+    private String name;
+    private String displayName;
+    private String description;
+    private String taskName;
+    private String projectPath;
+    private boolean isPublic;
+
+    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 List<String> getArgs() {
+        return Collections.singletonList(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;
+    }
+
+    public boolean isPublic() {
+        return isPublic;
+    }
+
+    public LaunchableGradleTaskSelector setPublic(boolean isPublic) {
+        this.isPublic = isPublic;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "LaunchableGradleTaskSelector{"
+                + "name='" + name + "' "
+                + "description='" + description + "'}";
+    }
+}
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 27ba3d0..4c70250 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
@@ -37,12 +37,12 @@ class ModuleDependencyTest extends Specification {
         new ModuleDependency("a", "").hashCode() == new ModuleDependency("a", "COMPILE").hashCode()
     }
 
-    def shouldExportForCompileAndRuntimeScope() {
+    def shouldNotExportDependencies() {
         expect:
-        new ModuleDependency("a", "COMPILE").exported
-        new ModuleDependency("a", "RUNTIME").exported
-        new ModuleDependency("a", "").exported
-        new ModuleDependency("a", null).exported
+        !(new ModuleDependency("a", "COMPILE").exported)
+        !(new ModuleDependency("a", "RUNTIME").exported)
+        !(new ModuleDependency("a", "").exported)
+        !(new ModuleDependency("a", null).exported)
         !(new ModuleDependency("a", "TEST").exported)
     }
-}
\ No newline at end of file
+}
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 3503b5e..7aa0d03 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
@@ -40,12 +40,12 @@ class ModuleLibraryTest extends Specification {
         new ModuleLibrary([] as Set, [] as Set, [] as Set, [] as Set, scope)
     }
 
-    def shouldExportForCompileAndRuntimeScope() {
+    def shouldNotExportDependencies() {
         expect:
-        createModuleLibraryWithScope("COMPILE").exported
-        createModuleLibraryWithScope("RUNTIME").exported
-        createModuleLibraryWithScope("").exported
-        createModuleLibraryWithScope(null).exported
+        !(createModuleLibraryWithScope("COMPILE").exported)
+        !(createModuleLibraryWithScope("RUNTIME").exported)
+        !(createModuleLibraryWithScope("").exported)
+        !(createModuleLibraryWithScope(null).exported)
         !(createModuleLibraryWithScope("TEST").exported)
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/integ-test/integ-test.gradle b/subprojects/integ-test/integ-test.gradle
index 846accd..59d1455 100644
--- a/subprojects/integ-test/integ-test.gradle
+++ b/subprojects/integ-test/integ-test.gradle
@@ -28,6 +28,10 @@ integTestTasks.all {
         systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
     }
 
+    // You can exclude the userguide samples by their ids by specifying this system property.
+    // E.g. ./gradlew integTest:integTest -D:integTest:integTest.single=UserGuideSamplesIntegrationTest -Dorg.gradle.userguide.samples.exclude=completeCUnitExample,nativeComponentReport
+    systemProperty "org.gradle.userguide.samples.exclude", System.getProperty("org.gradle.userguide.samples.exclude")
+
     // You can filter the userguide samples to be run by specifying this system property.
     // E.g. ./gradlew integTest:integTest -D:integTest:integTest.single=UserGuideSamplesIntegrationTest -Dorg.gradle.userguide.samples.filter=signing/.+
     systemProperty "org.gradle.userguide.samples.filter", System.getProperty("org.gradle.userguide.samples.filter")
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 2353e68..2e11493 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
@@ -18,11 +18,13 @@ package org.gradle.integtests;
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.executer.ArtifactBuilder;
 import org.gradle.integtests.fixtures.executer.ExecutionFailure;
+import org.gradle.test.fixtures.file.LeaksFileHandles;
 import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.junit.Assert.fail;
 
+ at LeaksFileHandles
 public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void providesADefaultBuildForBuildSrcProject() {
@@ -207,7 +209,7 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
     public void canInjectClassPathIntoSubProjects() {
         fail("implement me");
     }
-    
+
     @Test @Ignore
     public void canReuseClassPathRepositories() {
         fail("implement me");
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 6950f20..2fa44ea 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
@@ -17,7 +17,9 @@ package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.executer.ArtifactBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 public class CustomPluginIntegrationTest extends AbstractIntegrationSpec {
     public void "can reference plugin in buildSrc by id"() {
         given:
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 7208ca2..ce51030 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
@@ -16,6 +16,7 @@
 
 package org.gradle.integtests
 
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
 import org.gradle.util.DistributionLocator
 import org.gradle.util.GradleVersion
 import org.gradle.util.Requires
@@ -26,6 +27,7 @@ import spock.lang.Specification
 class DistributionLocatorIntegrationTest extends Specification {
 
     def locator = new DistributionLocator()
+    def distributions = new ReleasedVersionDistributions()
 
     def "locates release versions"() {
         expect:
@@ -37,7 +39,7 @@ class DistributionLocatorIntegrationTest extends Specification {
 
     def "locates snapshot versions"() {
         expect:
-        urlExist(locator.getDistributionFor(GradleVersion.version("2.5-20150412220016+0000")))
+        urlExist(locator.getDistributionFor(distributions.mostRecentSnapshot.version))
     }
 
     void urlExist(URI url) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedLegacyAndComponentJvmPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedLegacyAndComponentJvmPluginIntegrationTest.groovy
index 30bd7e6..6020aa7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedLegacyAndComponentJvmPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedLegacyAndComponentJvmPluginIntegrationTest.groovy
@@ -17,13 +17,18 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.test.fixtures.archive.JarTestFixture
 
 public class MixedLegacyAndComponentJvmPluginIntegrationTest extends AbstractIntegrationSpec {
 
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
     def "can combine legacy java and jvm-component plugins in a single project"() {
         settingsFile << "rootProject.name = 'test'"
-        buildFile << """
+        buildFile << '''
             apply plugin: "java"
             apply plugin: "jvm-component"
             apply plugin: "java-lang"
@@ -32,18 +37,22 @@ public class MixedLegacyAndComponentJvmPluginIntegrationTest extends AbstractInt
                 components {
                     jvmLib(JvmLibrarySpec)
                 }
-            }
+                tasks {
+                    create("checkModel") {
+                        def components = $("components")
+                        doLast {
+                            assert components.size() == 1
+                            assert components.jvmLib instanceof JvmLibrarySpec
 
-            task checkModel << {
-                assert componentSpecs.size() == 1
-                assert componentSpecs.jvmLib instanceof JvmLibrarySpec
-
-                assert binaries.size() == 3
-                assert binaries.jvmLibJar instanceof JarBinarySpec
-                assert binaries.mainClasses instanceof ClassDirectoryBinarySpec
-                assert binaries.testClasses instanceof ClassDirectoryBinarySpec
+                            assert project.binaries.size() == 3
+                            assert project.binaries.jvmLibJar instanceof JarBinarySpec
+                            assert project.binaries.mainClasses instanceof ClassDirectoryBinarySpec
+                            assert project.binaries.testClasses instanceof ClassDirectoryBinarySpec
+                        }
+                    }
+                }
             }
-"""
+'''
         expect:
         succeeds "checkModel"
     }
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
index b352a94..a220afe 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedNativeAndJvmProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedNativeAndJvmProjectIntegrationTest.groovy
@@ -16,11 +16,17 @@
 
 package org.gradle.integtests
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.internal.os.OperatingSystem
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.test.fixtures.archive.JarTestFixture
 
 public class MixedNativeAndJvmProjectIntegrationTest extends AbstractIntegrationSpec {
 
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
     def "can combine legacy java and cpp plugins in a single project"() {
         settingsFile << "rootProject.name = 'test'"
         buildFile << """
@@ -47,7 +53,7 @@ task checkBinaries << {
     }
 
     def "can combine jvm and native components in the same project"() {
-        buildFile << """
+        buildFile << '''
 plugins {
     id 'native-component'
     id 'jvm-component'
@@ -59,25 +65,30 @@ model {
         nativeLib(NativeLibrarySpec)
         jvmLib(JvmLibrarySpec)
     }
+    tasks {
+        create("validate") {
+            def components = $("components")
+            doLast {
+                assert components.size() == 3
+                assert components.nativeExe instanceof NativeExecutableSpec
+                assert components.nativeLib instanceof NativeLibrarySpec
+                assert components.jvmLib instanceof JvmLibrarySpec
+
+                assert project.binaries.size() == 4
+                assert project.binaries.jvmLibJar instanceof JarBinarySpec
+                assert project.binaries.nativeExeExecutable instanceof NativeExecutableBinarySpec
+                assert project.binaries.nativeLibStaticLibrary instanceof StaticLibraryBinarySpec
+                assert project.binaries.nativeLibSharedLibrary instanceof SharedLibraryBinarySpec
+            }
+        }
+    }
 }
-
-task validate << {
-    assert componentSpecs.size() == 3
-    assert componentSpecs.nativeExe instanceof NativeExecutableSpec
-    assert componentSpecs.nativeLib instanceof NativeLibrarySpec
-    assert componentSpecs.jvmLib instanceof JvmLibrarySpec
-
-    assert binaries.size() == 4
-    assert binaries.jvmLibJar instanceof JarBinarySpec
-    assert binaries.nativeExeExecutable instanceof NativeExecutableBinarySpec
-    assert binaries.nativeLibStaticLibrary instanceof StaticLibraryBinarySpec
-    assert binaries.nativeLibSharedLibrary instanceof SharedLibraryBinarySpec
-}
-"""
+'''
         expect:
         succeeds "validate"
     }
 
+    @RequiresInstalledToolChain
     def "build mixed components in one project"() {
         given:
         file("src/jvmLib/java/org/gradle/test/Test.java") << """
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 b86aa41..0da9974 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
@@ -64,6 +64,14 @@ sourceSets.each {
         scala.include "org/gradle/$name/**"
     }
 }
+
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.fork = true
+    scalaCompileOptions.useAnt = false
+    if(!JavaVersion.current().isJava8Compatible()) {
+        scalaCompileOptions.forkOptions.jvmArgs = ['-XX:MaxPermSize=512m']
+    }
+}
 '''
         file('src/org/gradle/main/resource.txt') << 'some text'
         file('src/org/gradle/test/resource.txt') << 'some text'
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
index 157f6ba..6790896 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskSubclassingBinaryCompatibilityCrossVersionSpec.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskSubclassingBinaryCompatibilityCrossVersionSpec.groovy
@@ -34,11 +34,14 @@ import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.plugins.ear.Ear
 import org.gradle.plugins.signing.Sign
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
+
 /**
  * Tests that task classes compiled against earlier versions of Gradle are still compatible.
  */
 @TargetVersions('1.0+')
 class TaskSubclassingBinaryCompatibilityCrossVersionSpec extends CrossVersionIntegrationSpec {
+    @LeaksFileHandles
     def "can use task subclass compiled using previous Gradle version"() {
         given:
         def taskClasses = [
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 033ea33..7987340 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
@@ -146,12 +146,14 @@ assert classesDir.directory
         out.contains("javaHome=" + alternateJavaHome.canonicalPath)
     }
 
-    @IgnoreIf({ AvailableJavaHomes.differentJdk == null})
+    @IgnoreIf({ AvailableJavaHomes.differentJdk == null })
     def "java home from gradle properties should be used to run build"() {
         def alternateJavaHome = AvailableJavaHomes.differentJdk.javaHome
 
-        file('gradle.properties') << "org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}"
-
+        file('gradle.properties') << """
+org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}
+org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions
+"""
         file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.absolutePath"
 
         when:
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 b3633d3..0d95f6a 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
@@ -27,4 +27,4 @@ class AutoTestedSamplesCoreIntegrationTest extends AbstractAutoTestedSamplesTest
 //        includeOnly '**/Copy.java'
         runSamplesFrom("subprojects/core/src/main/groovy/org/gradle/api")
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
index f0910d7..80c90dd 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
@@ -18,6 +18,7 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
@@ -40,6 +41,7 @@ class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
         result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
     }
 
+    @LeaksFileHandles
     public void canPublishAndUsePluginAndTestImplementations() {
         given:
         executer.inDirectory(producerDir).withTasks('uploadArchives').run()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesDependencySubstitutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesDependencySubstitutionIntegrationTest.groovy
new file mode 100644
index 0000000..5162571
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesDependencySubstitutionIntegrationTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.util.TextUtil
+import org.junit.Rule
+
+
+class SamplesDependencySubstitutionIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'dependency-substitution')
+
+    @UsesSample("dependency-substitution")
+    def "can run sample with all external dependencies" () {
+        given:
+        inDirectory "dependency-substitution"
+
+        when:
+        succeeds "showJarFiles"
+
+        then:
+        output.contains("project project1 is external to this build")
+        TextUtil.normaliseFileSeparators(output).contains("repo/org.example/project1/1.0/project1-1.0.jar")
+        output.contains("project project2 is external to this build")
+        TextUtil.normaliseFileSeparators(output).contains("repo/org.example/project2/1.0/project2-1.0.jar")
+        output.contains("project project3 is external to this build")
+        TextUtil.normaliseFileSeparators(output).contains("repo/org.example/project3/1.0/project3-1.0.jar")
+    }
+
+    @UsesSample("dependency-substitution")
+    def "can run sample with some internal projects" () {
+        given:
+        inDirectory "dependency-substitution"
+
+        when:
+        args("-DuseLocal=project1,project2")
+        succeeds "showJarFiles"
+
+        then:
+        output.contains("project project1 is INTERNAL to this build")
+        TextUtil.normaliseFileSeparators(output).contains("project1/build/libs/project1-1.0.jar")
+        output.contains("project project2 is INTERNAL to this build")
+        TextUtil.normaliseFileSeparators(output).contains("project2/build/libs/project2-1.0.jar")
+        output.contains("project project3 is external to this build")
+        TextUtil.normaliseFileSeparators(output).contains("repo/org.example/project3/1.0/project3-1.0.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 3536ccd..2ef6917 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
@@ -19,6 +19,7 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 import org.junit.Test
 
@@ -34,6 +35,7 @@ class SamplesGroovyMultiProjectIntegrationTest extends AbstractIntegrationTest {
     private List testFiles = ['JavaPersonTest', 'GroovyPersonTest', 'GroovyJavaPersonTest']
 
     @Test
+    @LeaksFileHandles
     public void groovyProjectSamples() {
         String packagePrefix = 'build/classes/main/org/gradle'
         String testPackagePrefix = 'build/classes/test/org/gradle'
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy
index 4263c25..a808b44 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec {
@@ -25,6 +26,7 @@ class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec
   @Rule public final Sample sample = new Sample(temporaryFolder)
 
   @UsesSample("multiProjectBuildSrc")
+  @LeaksFileHandles
   def "plugins from buildSrc subprojects are available"() {
     given:
     inDirectory "multiProjectBuildSrc"
@@ -37,4 +39,4 @@ class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec
     output.contains "pluginb.PluginB"
   }
 
-}
\ No newline at end of file
+}
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 ca5c187..22ca262 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
@@ -19,6 +19,7 @@ package org.gradle.integtests.samples
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
@@ -30,7 +31,7 @@ class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
         when:
         sample sample
         run 'clean', 'assemble'
-        
+
         then:
         TestFile tmpDir = file('unjar')
         sample.dir.file("build/libs/customized-1.0.war").unzipTo(tmpDir)
@@ -50,6 +51,7 @@ class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
                 'webapp.html')
     }
 
+    @LeaksFileHandles
     def "can execute servlet"() {
         given:
         // Inject some int test stuff
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesRunner.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesRunner.groovy
index c2058dd..e481c1a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesRunner.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesRunner.groovy
@@ -19,7 +19,7 @@ import com.google.common.collect.ArrayListMultimap
 import groovy.io.PlatformLineWriter
 import org.apache.tools.ant.taskdefs.Delete
 import org.gradle.api.Transformer
-import org.gradle.api.reporting.components.ComponentReportOutputFormatter
+import org.gradle.api.reporting.components.NativeComponentReportOutputFormatter
 import org.gradle.integtests.fixtures.executer.*
 import org.gradle.internal.SystemProperties
 import org.gradle.test.fixtures.file.TestFile
@@ -111,7 +111,11 @@ class UserGuideSamplesRunner extends Runner {
             }
             notifier.fireTestFinished(childDescription)
         }
-        temporaryFolder.testDirectory.deleteDir()
+        try {
+            temporaryFolder.testDirectory.deleteDir()
+        } catch (IOException e) {
+            //ignore
+        }
     }
 
     private void cleanup(SampleRun run) {
@@ -250,7 +254,7 @@ class UserGuideSamplesRunner extends Runner {
             sampleRun.runs << run
         }
 
-        samplesById.nativeComponentReport.runs.each { it.outputFormatter = new ComponentReportOutputFormatter() }
+        samplesById.nativeComponentReport.runs.each { it.outputFormatter = new NativeComponentReportOutputFormatter() }
 
         if ("true".equals(System.getProperty("org.gradle.integtest.unknownos"))) {
             // Ignore for now
diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle b/subprojects/internal-integ-testing/internal-integ-testing.gradle
index 31425fd..c11828a 100644
--- a/subprojects/internal-integ-testing/internal-integ-testing.gradle
+++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle
@@ -37,6 +37,7 @@ task prepareVersionsInfo(type: PrepareVersionsInfo) {
     destFile = new File(generatedResourcesDir, "all-released-versions.properties")
     versions = releasedVersions.allVersions
     mostRecent = releasedVersions.mostRecentFinalRelease
+    mostRecentSnapshot = releasedVersions.mostRecentSnapshot
 }
 
 sourceSets.main.output.dir generatedResourcesDir, builtBy: prepareVersionsInfo
@@ -45,10 +46,12 @@ class PrepareVersionsInfo extends DefaultTask {
     @OutputFile File destFile
     @Input String mostRecent
     @Input List<String> versions
+    @Input String mostRecentSnapshot
 
     @TaskAction void prepareVersions() {
         def properties = new Properties()
         properties.mostRecent = mostRecent
+        properties.mostRecentSnapshot = mostRecentSnapshot
         properties.versions = versions.join(' ')
         destFile.withOutputStream {
             properties.store(it, "")
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 0a55f15..8b3e90e 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
@@ -31,11 +31,12 @@ import spock.lang.Specification
 
 /**
  * Spockified version of AbstractIntegrationTest.
- * 
+ *
  * Plan is to bring features over as needed.
  */
 class AbstractIntegrationSpec extends Specification implements TestDirectoryProvider {
-    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider() {
+    @Rule
+    final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider() {
         @Override
         Statement apply(Statement base, FrameworkMethod method, Object target) {
             return super.apply(new Statement() {
@@ -138,30 +139,27 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     protected ExecutionFailure runAndFail(String... tasks) {
         fails(*tasks)
     }
-    
+
     protected ExecutionFailure fails(String... tasks) {
         failure = executer.withTasks(*tasks).runWithFailure()
         result = failure
     }
-    
+
     protected List<String> getExecutedTasks() {
         assertHasResult()
         result.executedTasks
     }
-    
+
     protected Set<String> getSkippedTasks() {
         assertHasResult()
         result.skippedTasks
     }
-    
+
     protected List<String> getNonSkippedTasks() {
         executedTasks - skippedTasks
     }
-    
+
     protected void executedAndNotSkipped(String... tasks) {
-        if (GradleContextualExecuter.parallel) {
-            return
-        }
         tasks.each {
             assert it in executedTasks
             assert !skippedTasks.contains(it)
@@ -169,9 +167,6 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     }
 
     protected void skipped(String... tasks) {
-        if (GradleContextualExecuter.parallel) {
-            return
-        }
         tasks.each {
             assert it in executedTasks
             assert skippedTasks.contains(it)
@@ -201,9 +196,9 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     protected void failureDescriptionContains(String description) {
         failure.assertThatDescription(CoreMatchers.containsString(description))
     }
-    
+
     private assertHasResult() {
-        assert result != null : "result is null, you haven't run succeeds()"
+        assert result != null: "result is null, you haven't run succeeds()"
     }
 
     String getOutput() {
@@ -239,14 +234,14 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
         return mavenRepo
     }
 
-    public MavenFileRepository publishedMavenModules(String ... modulesToPublish) {
+    public MavenFileRepository publishedMavenModules(String... modulesToPublish) {
         modulesToPublish.each { String notation ->
             def modules = notation.split("->").reverse()
             def current
             modules.each { String module ->
                 def s = new TestDependency(module)
                 def m = mavenRepo.module(s.group, s.name, s.version)
-                current = current? m.dependsOn(current.groupId, current.artifactId, current.version).publish() : m.publish()
+                current = current ? m.dependsOn(current.groupId, current.artifactId, current.version).publish() : m.publish()
             }
         }
         mavenRepo
@@ -283,4 +278,9 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
         TestFile root = file(name)
         root.create(cl)
     }
-}
\ No newline at end of file
+
+    void outputContains(String string) {
+        assertHasResult()
+        result.assertOutputContains(string.trim())
+    }
+}
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 9edb481..1da5422 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,10 +15,16 @@
  */
 package org.gradle.integtests.fixtures;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+import net.rubygrapefruit.platform.SystemInfo;
+import net.rubygrapefruit.platform.WindowsRegistry;
 import org.gradle.api.JavaVersion;
 import org.gradle.api.Nullable;
-import org.gradle.api.Transformer;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
 import org.gradle.integtests.fixtures.jvm.InstalledJvmLocator;
 import org.gradle.integtests.fixtures.jvm.JvmInstallation;
 import org.gradle.internal.SystemProperties;
@@ -26,6 +32,7 @@ import org.gradle.internal.jvm.JavaInfo;
 import org.gradle.internal.jvm.Jre;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeintegration.filesystem.FileCanonicalizer;
+import org.gradle.internal.nativeintegration.services.NativeServices;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 import org.gradle.util.CollectionUtils;
@@ -46,77 +53,63 @@ abstract public class AvailableJavaHomes {
         return getJdk(JavaVersion.VERSION_1_5);
     }
 
-    /**
-     * Locates a JDK installation for the given version.
-     *
-     * @return null if not found.
-     */
     @Nullable
-    public static JavaInfo getJdk(JavaVersion version) {
-        for (JvmInstallation candidate : getJvms()) {
-            if (candidate.getJavaVersion().equals(version) && candidate.isJdk()) {
-                return Jvm.forHome(candidate.getJavaHome());
-            }
-        }
-        return null;
+    public static JavaInfo getJdk6() {
+        return getJdk(JavaVersion.VERSION_1_6);
     }
 
-    /**
-     * Provides all available JDK installations.
-     *
-     * @return empty list if no JDK can be found.
-     */
-    public static List<JavaInfo> getAvailableJdks() {
-        return CollectionUtils.collect(getJvms(), new Transformer<JavaInfo, JvmInstallation>() {
-            public JavaInfo transform(JvmInstallation candidate) {
-                return Jvm.forHome(candidate.getJavaHome());
+    @Nullable
+    public static JavaInfo getJdk(final JavaVersion version) {
+        return getAvailableJdk(new Spec<JvmInstallation>() {
+            @Override
+            public boolean isSatisfiedBy(JvmInstallation element) {
+                return version.equals(element.getJavaVersion());
             }
         });
     }
 
-    /**
-     * Locates a JDK installation that is different to the current JVM, ie for which java.home is different.
-     *
-     * @return null if not found.
-     */
+    public static List<JavaInfo> getAvailableJdks() {
+        return getAvailableJdks(Specs.satisfyAll());
+    }
+
+    public static List<JavaInfo> getAvailableJdks(final Spec<? super JvmInstallation> filter) {
+        return FluentIterable.from(getJvms())
+            .filter(new Predicate<JvmInstallation>() {
+                @Override
+                public boolean apply(JvmInstallation input) {
+                    return input.isJdk() && filter.isSatisfiedBy(input);
+                }
+            })
+            .transform(new Function<JvmInstallation, JavaInfo>() {
+                @Override
+                public JavaInfo apply(JvmInstallation input) {
+                    return Jvm.forHome(input.getJavaHome());
+                }
+            }).toList();
+    }
+
+    public static JavaInfo getAvailableJdk(final Spec<? super JvmInstallation> filter) {
+        return Iterables.getFirst(getAvailableJdks(filter), null);
+    }
+
     @Nullable
     public static JavaInfo getDifferentJdk() {
-        Jvm jvm = Jvm.current();
-        for (JvmInstallation candidate : getJvms()) {
-            if (candidate.getJavaHome().equals(jvm.getJavaHome())) {
-                continue;
-            }
-
-            // Currently tests implicitly assume a JDK
-            if (!candidate.isJdk()) {
-                continue;
+        return getAvailableJdk(new Spec<JvmInstallation>() {
+            @Override
+            public boolean isSatisfiedBy(JvmInstallation element) {
+                return !element.getJavaHome().equals(Jvm.current().getJavaHome());
             }
-            return Jvm.forHome(candidate.getJavaHome());
-        }
-
-        return null;
+        });
     }
 
-    /**
-     * Locates a JDK installation that has a different version to the current JVM, ie for which java.version is different.
-     *
-     * @return null if not found.
-     */
     @Nullable
     public static JavaInfo getDifferentVersion() {
-        Jvm jvm = Jvm.current();
-        for (JvmInstallation candidate : getJvms()) {
-            if (candidate.getJavaVersion().equals(jvm.getJavaVersion())) {
-                continue;
-            }
-            // Currently tests implicitly assume a JDK
-            if (!candidate.isJdk()) {
-                continue;
+        return getAvailableJdk(new Spec<JvmInstallation>() {
+            @Override
+            public boolean isSatisfiedBy(JvmInstallation element) {
+                return !element.getJavaVersion().equals(Jvm.current().getJavaVersion());
             }
-            return Jvm.forHome(candidate.getJavaHome());
-        }
-
-        return null;
+        });
     }
 
     /**
@@ -139,10 +132,12 @@ abstract public class AvailableJavaHomes {
 
     private static List<JvmInstallation> getJvms() {
         if (jvms == null) {
-            FileCanonicalizer fileCanonicalizer = NativeServicesTestFixture.getInstance().get(FileCanonicalizer.class);
+            NativeServices nativeServices = NativeServicesTestFixture.getInstance();
+            FileCanonicalizer fileCanonicalizer = nativeServices.get(FileCanonicalizer.class);
             jvms = new ArrayList<JvmInstallation>();
             jvms.addAll(new DevInfrastructureJvmLocator(fileCanonicalizer).findJvms());
-            jvms.addAll(new InstalledJvmLocator().findJvms());
+            InstalledJvmLocator installedJvmLocator = new InstalledJvmLocator(OperatingSystem.current(), Jvm.current(), nativeServices.get(WindowsRegistry.class), nativeServices.get(SystemInfo.class), fileCanonicalizer);
+            jvms.addAll(installedJvmLocator.findJvms());
             jvms.addAll(new HomeDirJvmLocator(fileCanonicalizer).findJvms());
             // Order from most recent to least recent
             Collections.sort(jvms, new Comparator<JvmInstallation>() {
@@ -182,7 +177,7 @@ abstract public class AvailableJavaHomes {
         }
 
         private List<JvmInstallation> addJvm(List<JvmInstallation> jvms, JavaVersion javaVersion, String versionString, File javaHome, boolean jdk, JvmInstallation.Arch arch) {
-            if(javaHome.exists()) {
+            if (javaHome.exists()) {
                 jvms.add(new JvmInstallation(javaVersion, versionString, fileCanonicalizer.canonicalize(javaHome), jdk, arch));
             }
             return jvms;
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FluidDependenciesResolveRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FluidDependenciesResolveRunner.groovy
new file mode 100644
index 0000000..a694f00
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FluidDependenciesResolveRunner.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures
+import org.gradle.integtests.fixtures.executer.AbstractGradleExecuter
+/**
+ * Runs tests with fluid dependencies enabled and disabled: when fluid dependencies are enabled, any configuration that
+ * is a task input is resolved when constructing the task graph.
+ * Test classes that use this runner will have tests run twice, with and without fluid dependencies enabled.
+ */
+public class FluidDependenciesResolveRunner extends AbstractMultiTestRunner {
+    public final static String ASSUME_FLUID_DEPENDENCIES = "org.gradle.resolution.assumeFluidDependencies"
+
+    public FluidDependenciesResolveRunner(Class<?> target) {
+        super(target)
+        // Ensure that the system property is propagated to forked Gradle executions
+        AbstractGradleExecuter.propagateSystemProperty(ASSUME_FLUID_DEPENDENCIES);
+    }
+
+    @Override
+    protected void createExecutions() {
+        // Run the test once with early dependency forced and once without
+        add(new ResolveDependencyGraphExecution(true))
+        add(new ResolveDependencyGraphExecution(false))
+    }
+
+    private class ResolveDependencyGraphExecution extends AbstractMultiTestRunner.Execution {
+        final boolean assumeFluidDependencies
+
+        public ResolveDependencyGraphExecution(boolean assumeFluidDependencies) {
+            this.assumeFluidDependencies = assumeFluidDependencies
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return assumeFluidDependencies ? "with fluid dependencies" : "without fluid dependencies"
+        }
+
+        @Override
+        protected void before() {
+            System.setProperty(ASSUME_FLUID_DEPENDENCIES, String.valueOf(assumeFluidDependencies))
+        }
+
+        @Override
+        protected void after() {
+            System.properties.remove(ASSUME_FLUID_DEPENDENCIES)
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkScalaCompileInDaemonModeFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkScalaCompileInDaemonModeFixture.groovy
index 743b862..969e97e 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkScalaCompileInDaemonModeFixture.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkScalaCompileInDaemonModeFixture.groovy
@@ -36,6 +36,9 @@ allprojects {
     tasks.withType(ScalaCompile) {
         scalaCompileOptions.fork = true
         scalaCompileOptions.useAnt = false
+        if(!JavaVersion.current().isJava8Compatible()) {
+            scalaCompileOptions.forkOptions.jvmArgs = ['-XX:MaxPermSize=512m']
+        }
     }
     tasks.withType(ScalaDoc) {
         doFirst {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PersistentBuildProcessIntegrationTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PersistentBuildProcessIntegrationTest.groovy
new file mode 100644
index 0000000..7a8de51
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PersistentBuildProcessIntegrationTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures
+
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.util.Requires
+
+/**
+ * Base class for test that test the behaviour of Gradle when running subsequent builds in the same build process.
+ */
+ at Requires(adhoc = { GradleContextualExecuter.longLivingProcess })
+class PersistentBuildProcessIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        executer.requireIsolatedDaemons()
+    }
+
+    @Override
+    protected void cleanupWhileTestFilesExist() {
+        if (GradleContextualExecuter.daemon) {
+            executer.withArguments("-i", "--stop").run()
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/AbstractDaemonFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/AbstractDaemonFixture.groovy
new file mode 100644
index 0000000..ff986fb
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/AbstractDaemonFixture.groovy
@@ -0,0 +1,134 @@
+/*
+ * 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.fixtures.daemon
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.process.internal.ExecHandleBuilder
+import org.gradle.process.internal.streams.SafeStreams
+
+abstract class AbstractDaemonFixture implements DaemonFixture {
+    public static final int STATE_CHANGE_TIMEOUT = 20000
+    final DaemonContext context
+
+    AbstractDaemonFixture(File daemonLog) {
+        this.context = DaemonContextParser.parseFrom(daemonLog.text)
+        if (this.context.pid == null) {
+            println "PID in daemon log ($daemonLog.absolutePath) is null."
+            println "daemon.log exists: ${daemonLog.exists()}"
+
+            println "start daemon.log content: "
+            println "{daemonLog.text.isEmpty()}) = ${daemonLog.text.isEmpty()})"
+            println daemonLog.text;
+            println "end daemon.log content"
+
+        }
+    }
+
+    void becomesIdle() {
+        waitForState(State.idle)
+    }
+
+    void stops() {
+        waitForState(State.stopped)
+    }
+
+    @Override
+    void assertIdle() {
+        assertHasState(State.idle)
+    }
+
+    @Override
+    void assertBusy() {
+        assertHasState(State.busy)
+    }
+
+    @Override
+    void assertStopped() {
+        assertHasState(State.stopped)
+    }
+
+    protected abstract void waitForState(State state)
+
+    protected abstract void assertHasState(State state)
+
+    /**
+     * Forcefully kills this daemon.
+     */
+    void kill() {
+        Long pid = context.pid
+        println "Killing daemon with pid: $pid"
+        if (pid == null) {
+            throw new RuntimeException("Unable to force kill the daemon because provided pid is null!")
+        }
+        if (!(OperatingSystem.current().unix || OperatingSystem.current().windows)) {
+            throw new RuntimeException("This implementation does not know how to forcefully kill the daemon on os: " + OperatingSystem.current())
+        }
+        def output = new ByteArrayOutputStream()
+        def e = new ExecHandleBuilder()
+                .commandLine(killArgs(pid))
+                .redirectErrorStream()
+                .setStandardInput(killScript(pid))
+                .setStandardOutput(output)
+                .workingDir(new File(".").absoluteFile) //does not matter
+                .build()
+        e.start()
+        def result = e.waitForFinish()
+        result.rethrowFailure()
+    }
+
+    private static Object[] killArgs(Long pid) {
+        if (OperatingSystem.current().unix) {
+            // start shell, read script from stdin
+            return ["bash"]
+        } else if (OperatingSystem.current().windows) {
+            // '/T' kills full process tree
+            // TODO: '/T' option should be removed after fixing GRADLE-3298
+            return ["taskkill.exe", "/F", "/T", "/PID", pid]
+        } else {
+            throw new IllegalStateException()
+        }
+    }
+
+    private static InputStream killScript(Long pid) {
+        if (OperatingSystem.current().unix) {
+            // script for killing full process tree
+            // TODO: killing full process tree should be removed after fixing GRADLE-3298
+            // this script is tested on Linux and MacOSX
+            def killScript = '''
+killtree() {
+    local _pid=$1
+    for _child in $(ps -o pid,ppid -ax | awk "{ if ( \\$2 == ${_pid} ) { print \\$1 }}"); do
+        killtree ${_child}
+    done
+    kill -9 ${_pid}
+}
+'''
+            killScript += "\nkilltree $pid\n"
+            return new ByteArrayInputStream(killScript.getBytes())
+        } else if (OperatingSystem.current().windows) {
+            return SafeStreams.emptyInput()
+        } else {
+            throw new IllegalStateException()
+        }
+    }
+
+    @SuppressWarnings("FieldName")
+    enum State {
+        busy, idle, stopped
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonContextParser.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonContextParser.java
new file mode 100644
index 0000000..1e21daa
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonContextParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.daemon;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.context.DefaultDaemonContext;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DaemonContextParser {
+    public static DaemonContext parseFromFile(File file) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(file));
+            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+                DaemonContext context = parseFrom(line);
+                if (context != null) {
+                    return context;
+                }
+            }
+        } catch(FileNotFoundException e) {
+            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].", e);
+        } catch (IOException e) {
+            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].", e);
+        }
+        throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].");
+    }
+
+    public static DaemonContext parseFromString(String source) {
+        DaemonContext context = parseFrom(source);
+        if (context == null) {
+            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + source + "].");
+        }
+        return context;
+    }
+
+    private static DaemonContext parseFrom(String source) {
+        Pattern pattern = Pattern.compile("^.*DefaultDaemonContext\\[(uid=[^\\n,]+)?,?javaHome=([^\\n]+),daemonRegistryDir=([^\\n]+),pid=([^\\n]+),idleTimeout=(.+?),daemonOpts=([^\\n]+)].*",
+                Pattern.MULTILINE + Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(source);
+
+        if (matcher.matches()) {
+            String uid = matcher.group(1) == null ? null : matcher.group(1).substring("uid=".length());
+            String javaHome = matcher.group(2);
+            String daemonRegistryDir = matcher.group(3);
+            String pidStr = matcher.group(4);
+            Long pid = pidStr.equals("null") ? null : Long.parseLong(pidStr);
+            Integer idleTimeout = Integer.decode(matcher.group(5));
+            List<String> jvmOpts = Lists.newArrayList(Splitter.on(',').split(matcher.group(6)));
+            return new DefaultDaemonContext(uid, new File(javaHome), new File(daemonRegistryDir), pid, idleTimeout, jvmOpts);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonFixture.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonFixture.java
new file mode 100644
index 0000000..8f1e104
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonFixture.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.integtests.fixtures.daemon;
+
+public interface DaemonFixture {
+    /**
+     * Returns the TCP port used by this daemon.
+     */
+    int getPort();
+
+    /**
+     * Forcefully kills this daemon.
+     */
+    void kill();
+
+    /**
+     * Asserts that this daemon becomes idle within a short timeout. Blocks until this has happened.
+     */
+    void becomesIdle();
+
+    /**
+     * Asserts that this daemon stops and is no longer visible to any clients within a short timeout. Blocks until this has happened.
+     */
+    void stops();
+
+    /**
+     * Asserts that this daemon is currently idle.
+     */
+    void assertIdle();
+
+    /**
+     * Asserts that this daemon is currently busy.
+     */
+    void assertBusy();
+
+    /**
+     * Asserts that this daemon has stopped and is no longer visible to any clients.
+     */
+    void assertStopped();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonIntegrationSpec.groovy
new file mode 100644
index 0000000..256ab4c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonIntegrationSpec.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.daemon
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.test.fixtures.file.LeaksFileHandles
+import spock.lang.IgnoreIf
+
+ at IgnoreIf({ GradleContextualExecuter.daemon })
+ at LeaksFileHandles
+abstract class DaemonIntegrationSpec extends AbstractIntegrationSpec {
+
+    @Override
+    DaemonGradleExecuter getExecuter() {
+        super.executer as DaemonGradleExecuter
+    }
+
+    def setup() {
+        executer = new DaemonGradleExecuter(distribution, temporaryFolder)
+        executer.requireIsolatedDaemons()
+    }
+
+    @Override
+    protected void cleanupWhileTestFilesExist() {
+        // Need to kill daemons before test files are cleaned up, as the log files and registry are used to locate the daemons and these live under
+        // the test file directory.
+        daemons.killAll()
+    }
+
+    void stopDaemonsNow() {
+        result = executer.withArguments("--stop", "--info").run()
+    }
+
+    void buildSucceeds(String script = '') {
+        file('build.gradle') << script
+        result = executer.withArguments("--info").withNoDefaultJvmArgs().run()
+    }
+
+    DaemonsFixture getDaemons() {
+        new DaemonLogsAnalyzer(executer.daemonBaseDir)
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogFileStateProbe.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogFileStateProbe.groovy
new file mode 100644
index 0000000..2ddd39f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogFileStateProbe.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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.fixtures.daemon
+
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.integtests.fixtures.daemon.AbstractDaemonFixture.State
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class DaemonLogFileStateProbe implements DaemonStateProbe {
+    private final DaemonContext context
+    private final File log
+    private final String startBuildMessage
+    private final String finishBuildMessage
+
+    DaemonLogFileStateProbe(File daemonLog, DaemonContext context, String startBuildMessage = DaemonMessages.STARTED_BUILD, String finishBuildMessage = DaemonMessages.FINISHED_BUILD) {
+        this.finishBuildMessage = finishBuildMessage
+        this.startBuildMessage = startBuildMessage
+        this.log = daemonLog
+        this.context = context
+    }
+
+    @Override
+    String toString() {
+        return "DaemonLogFile{file: ${log}, context: ${context}}"
+    }
+
+    DaemonContext getContext() {
+        return context
+    }
+
+    State getCurrentState() {
+        getStates().last()
+    }
+
+    List<State> getStates() {
+        def states = new LinkedList<State>()
+        states << State.idle
+        log.eachLine {
+            if (it.contains(startBuildMessage)) {
+                states << State.busy
+            } else if (it.contains(finishBuildMessage)) {
+                states << State.idle
+            } else if (it.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)) {
+                states << State.stopped
+            }
+        }
+        states
+    }
+
+    String getLog() {
+        return log.text
+    }
+
+    int getPort() {
+        Pattern pattern = Pattern.compile("^.*" + DaemonMessages.ADVERTISING_DAEMON + ".*port:(\\d+).*",
+                Pattern.MULTILINE + Pattern.DOTALL);
+
+        Matcher matcher = pattern.matcher(log.text);
+        assert matcher.matches(): "Unable to find daemon address in the daemon log. Daemon: $context"
+
+        try {
+            return Integer.parseInt(matcher.group(1))
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("Unexpected format of the port number found in the daemon log. Daemon: $context")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogsAnalyzer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogsAnalyzer.groovy
new file mode 100644
index 0000000..a9a214d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogsAnalyzer.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.fixtures.daemon
+
+import org.gradle.internal.service.ServiceRegistryBuilder
+import org.gradle.internal.service.scopes.GlobalScopeServices
+import org.gradle.launcher.daemon.client.DaemonClientGlobalServices
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices
+import org.gradle.logging.LoggingServiceRegistry
+import org.gradle.testfixtures.internal.NativeServicesTestFixture
+import org.gradle.util.GradleVersion
+
+class DaemonLogsAnalyzer implements DaemonsFixture {
+    private final File daemonLogsDir
+    private final File daemonBaseDir
+    private final DaemonRegistry registry
+    private final String version
+
+    DaemonLogsAnalyzer(File daemonBaseDir, String version = GradleVersion.current().version) {
+        this.version = version
+        this.daemonBaseDir = daemonBaseDir
+        daemonLogsDir = new File(daemonBaseDir, version)
+        def services = ServiceRegistryBuilder.builder()
+                .parent(LoggingServiceRegistry.newEmbeddableLogging())
+                .parent(NativeServicesTestFixture.getInstance())
+                .provider(new GlobalScopeServices(false))
+                .provider(new DaemonClientGlobalServices())
+                .provider(new DaemonRegistryServices(daemonBaseDir))
+                .build()
+        registry = services.get(DaemonRegistry)
+    }
+
+    static DaemonsFixture newAnalyzer(File daemonBaseDir, String version = GradleVersion.current().version) {
+        return new DaemonLogsAnalyzer(daemonBaseDir, version)
+    }
+
+    DaemonRegistry getRegistry() {
+        return registry
+    }
+
+    void killAll() {
+        daemons*.kill()
+    }
+
+    List<DaemonFixture> getDaemons() {
+        assert daemonLogsDir.isDirectory()
+        return daemonLogsDir.listFiles().findAll { it.name.endsWith('.log') }.collect { daemonForLogFile(it) }
+    }
+
+    List<DaemonFixture> getVisible() {
+        return registry.all.collect { daemonForLogFile(new File(daemonLogsDir, "daemon-${it.pid}.out.log")) }
+    }
+
+    DaemonFixture daemonForLogFile(File logFile) {
+        if (version == GradleVersion.current().version) {
+            return new TestableDaemon(logFile, registry)
+        }
+        return new LegacyDaemon(logFile, version)
+    }
+
+    DaemonFixture getDaemon() {
+        def daemons = getDaemons()
+        assert daemons.size() == 1
+        daemons[0]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonRegistryStateProbe.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonRegistryStateProbe.groovy
new file mode 100644
index 0000000..ec8cd60
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonRegistryStateProbe.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.integtests.fixtures.daemon
+
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.integtests.fixtures.daemon.AbstractDaemonFixture.State
+
+class DaemonRegistryStateProbe implements DaemonStateProbe {
+    private final DaemonRegistry registry
+    private final DaemonContext context
+
+    DaemonRegistryStateProbe(DaemonRegistry registry, DaemonContext context) {
+        this.context = context
+        this.registry = registry
+    }
+
+    @Override
+    State getCurrentState() {
+        def daemonInfo = registry.all.find { it.context.pid == context.pid }
+        if (daemonInfo == null) {
+            return State.stopped
+        }
+        return daemonInfo.idle ? State.idle : State.busy
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonStateProbe.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonStateProbe.java
new file mode 100644
index 0000000..974387c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonStateProbe.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.integtests.fixtures.daemon;
+
+public interface DaemonStateProbe {
+    TestableDaemon.State getCurrentState();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonsFixture.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonsFixture.java
new file mode 100644
index 0000000..bb8b1cd
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonsFixture.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.integtests.fixtures.daemon;
+
+import java.util.List;
+
+public interface DaemonsFixture {
+    /**
+     * Kills all daemons.
+     */
+    void killAll();
+
+    /**
+     * Returns all known daemons. Includes any daemons that are no longer running.
+     */
+    List<? extends DaemonFixture> getDaemons();
+
+    /**
+     * Returns all daemons that are visible to clients. May include daemons that are no longer running (eg they have crashed).
+     */
+    List<? extends DaemonFixture> getVisible();
+
+    /**
+     * Convenience to get a single daemon. Fails if there is not exactly 1 daemon.
+     */
+    DaemonFixture getDaemon();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/LegacyDaemon.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/LegacyDaemon.groovy
new file mode 100644
index 0000000..1794ee0
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/LegacyDaemon.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.integtests.fixtures.daemon
+
+import org.gradle.util.GradleVersion
+
+class LegacyDaemon extends AbstractDaemonFixture {
+    private final DaemonLogFileStateProbe logFileProbe
+
+    LegacyDaemon(File daemonLog, String version) {
+        super(daemonLog)
+        if (GradleVersion.version(version).baseVersion >= GradleVersion.version("2.2")) {
+            logFileProbe = new DaemonLogFileStateProbe(daemonLog, context)
+        } else {
+            logFileProbe = new DaemonLogFileStateProbe(daemonLog, context, "Daemon is busy, sleeping until state changes", "Daemon is idle, sleeping until state change")
+        }
+    }
+
+    protected void waitForState(State state) {
+        def expiry = System.currentTimeMillis() + STATE_CHANGE_TIMEOUT
+        def lastLogState = logFileProbe.currentState
+        while (expiry > System.currentTimeMillis() && lastLogState != state) {
+            Thread.sleep(200)
+            lastLogState = logFileProbe.currentState
+        }
+        if (lastLogState == state) {
+            return
+        }
+        throw new AssertionError("""Timeout waiting for daemon with pid ${context.pid} to reach state ${state}.
+Current state is ${lastLogState}.""")
+    }
+
+    @Override
+    protected void assertHasState(State state) {
+        assert logFileProbe.currentState == state
+    }
+
+    @Override
+    int getPort() {
+        throw new UnsupportedOperationException()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/TestableDaemon.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/TestableDaemon.groovy
new file mode 100644
index 0000000..e4fc349
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/TestableDaemon.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.daemon
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+
+class TestableDaemon extends AbstractDaemonFixture {
+    private final DaemonLogFileStateProbe logFileProbe
+    private final DaemonRegistryStateProbe registryProbe
+
+    TestableDaemon(File daemonLog, DaemonRegistry registry) {
+        super(daemonLog)
+        this.logFileProbe = new DaemonLogFileStateProbe(daemonLog, context)
+        this.registryProbe = new DaemonRegistryStateProbe(registry, context)
+    }
+
+    protected void waitForState(State state) {
+        def expiry = System.currentTimeMillis() + STATE_CHANGE_TIMEOUT
+        def lastRegistryState = registryProbe.currentState
+        def lastLogState = logFileProbe.currentState
+        while (expiry > System.currentTimeMillis() && (lastRegistryState != state || lastLogState != state)) {
+            Thread.sleep(200)
+            lastRegistryState = registryProbe.currentState
+            lastLogState = logFileProbe.currentState
+        }
+        if (lastRegistryState == state && lastLogState == state) {
+            return
+        }
+        throw new AssertionError("""Timeout waiting for daemon with pid ${context.pid} to reach state ${state}.
+Current registry state is ${lastRegistryState} and current log state is ${lastLogState}.""")
+    }
+
+    @Override
+    protected void assertHasState(State state) {
+        assert logFileProbe.currentState == state
+        assert registryProbe.currentState == state
+    }
+
+    String getLog() {
+        return logFileProbe.log
+    }
+
+    int getPort() {
+        return logFileProbe.port
+    }
+}
\ No newline at end of file
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 598a0ec..b1ce71b 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
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.fixtures.executer;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.internal.ClosureBackedAction;
@@ -22,6 +24,7 @@ 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;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.configuration.GradleProperties;
 import org.gradle.listener.ActionBroadcast;
 import org.gradle.process.internal.JvmOptions;
@@ -41,6 +44,18 @@ import static org.gradle.util.Matchers.containsLine;
 import static org.gradle.util.Matchers.matchesRegexp;
 
 public abstract class AbstractGradleExecuter implements GradleExecuter {
+    protected static Set<String> propagatedSystemProperties = Sets.newHashSet();
+
+    public static void propagateSystemProperty(String name) {
+        propagatedSystemProperties.add(name);
+    }
+
+    private static final String DEBUG_SYSPROP = "org.gradle.integtest.debug";
+
+    protected static final List<String> DEBUG_ARGS = ImmutableList.of(
+        "-Xdebug",
+        "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"
+    );
 
     private final Logger logger;
 
@@ -84,6 +99,9 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private final TestDirectoryProvider testDirectoryProvider;
     private final GradleDistribution distribution;
 
+    private boolean debug = Boolean.getBoolean(DEBUG_SYSPROP);
+    protected boolean interactive;
+
     protected AbstractGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
         this.distribution = distribution;
         this.testDirectoryProvider = testDirectoryProvider;
@@ -116,6 +134,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         noDefaultJvmArgs = false;
         deprecationChecksOn = true;
         stackTraceChecksOn = true;
+        debug = Boolean.getBoolean(DEBUG_SYSPROP);
+        interactive = false;
         return this;
     }
 
@@ -227,6 +247,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
             executer.withDaemonStartingMessageEnabled();
         }
 
+        executer.withDebug(debug);
+        executer.withForceInteractive(interactive);
         return executer;
     }
 
@@ -271,6 +293,18 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
      * Returns the gradle opts set with withGradleOpts() (does not consider any set via withEnvironmentVars())
      */
     protected List<String> getGradleOpts() {
+        List<String> gradleOpts = new ArrayList<String>(this.gradleOpts);
+        for (Map.Entry<String, String> entry : getImplicitJvmSystemProperties().entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            gradleOpts.add(String.format("-D%s=%s", key, value));
+        }
+        gradleOpts.add("-ea");
+
+        if (isDebug()) {
+            gradleOpts.addAll(DEBUG_ARGS);
+        }
+
         return gradleOpts;
     }
 
@@ -555,6 +589,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
             properties.put(DefaultClassLoaderScope.STRICT_MODE_PROPERTY, "true");
         }
 
+        if (interactive) {
+            properties.put(DaemonParameters.INTERACTIVE_TOGGLE, "true");
+        }
+
         return properties;
     }
 
@@ -711,4 +749,21 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     public boolean isDaemonStartingMessageDisabled() {
         return daemonStartingMessageDisabled;
     }
+
+    @Override
+    public GradleExecuter withDebug(boolean flag) {
+        debug = flag;
+        return this;
+    }
+
+    @Override
+    public GradleExecuter withForceInteractive(boolean flag) {
+        interactive = flag;
+        return this;
+    }
+
+    @Override
+    public boolean isDebug() {
+        return debug;
+    }
 }
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 fe30ff8..f94f303 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
@@ -55,6 +55,12 @@ public class DaemonGradleExecuter extends ForkingGradleExecuter {
     @Override
     protected List<String> getGradleOpts() {
         List<String> gradleOpts = new ArrayList<String>(super.getGradleOpts());
+
+        gradleOpts.removeAll(DEBUG_ARGS);
+        if (isDebug()) {
+            gradleOpts.add("-Dorg.gradle.daemon.debug=true");
+        }
+
         if (isDaemonStartingMessageDisabled()) {
             gradleOpts.add("-D" + DISABLE_STARTING_DAEMON_MESSAGE_PROPERTY + "=true");
         }
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 91aaf92..8a19b0b 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
@@ -25,11 +25,13 @@ public interface ExecutionResult {
 
     ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder);
 
+    ExecutionResult assertOutputContains(String expectedOutput);
+
     /**
      * Returns the tasks have been executed in order (includes tasks that were skipped). Note: ignores buildSrc tasks.
      */
     List<String> getExecutedTasks();
-    
+
     /**
      * Asserts that exactly the given set of tasks have been executed in the given order. Note: ignores buildSrc tasks.
      */
@@ -39,7 +41,7 @@ public interface ExecutionResult {
      * Returns the tasks that were skipped, in an undefined order. Note: ignores buildSrc tasks.
      */
     Set<String> getSkippedTasks();
-    
+
     /**
      * Asserts that exactly the given set of tasks have been skipped. Note: ignores buildSrc tasks.
      */
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 5b654c8..617618a 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
@@ -61,14 +61,24 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
         List<String> args = new ArrayList<String>();
         args.addAll(super.getAllArgs());
         args.add("--stacktrace");
+        addPropagatedSystemProperties(args);
         return args;
     }
 
+    private void addPropagatedSystemProperties(List<String> args) {
+        for (String propName : propagatedSystemProperties) {
+            String propValue = System.getProperty(propName);
+            if (propValue != null) {
+                args.add("-D" + propName + "=" + propValue);
+            }
+        }
+    }
+
     private ExecHandleBuilder createExecHandleBuilder() {
         TestFile gradleHomeDir = getDistribution().getGradleHomeDir();
         if (!gradleHomeDir.isDirectory()) {
             fail(gradleHomeDir + " is not a directory.\n"
-                    + "If you are running tests from IDE make sure that gradle tasks that prepare the test image were executed. Last time it was 'intTestImage' task.");
+                + "If you are running tests from IDE make sure that gradle tasks that prepare the test image were executed. Last time it was 'intTestImage' task.");
         }
 
         NativeServicesTestFixture.initialize();
@@ -122,23 +132,6 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
         return start().waitForFailure();
     }
 
-    @Override
-    protected List<String> getGradleOpts() {
-        List<String> gradleOpts = new ArrayList<java.lang.String>(super.getGradleOpts());
-        for (Map.Entry<String, String> entry : getImplicitJvmSystemProperties().entrySet()) {
-            String key = entry.getKey();
-            String value = entry.getValue();
-            gradleOpts.add(String.format("-D%s=%s", key, value));
-        }
-        gradleOpts.add("-ea");
-
-        //uncomment for debugging
-//        gradleOpts.add("-Xdebug");
-//        gradleOpts.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
-
-        return gradleOpts;
-    }
-
     private interface ExecHandlerConfigurer {
         void configure(ExecHandleBuilder builder);
     }
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 e7d619e..a7ef712 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
@@ -94,8 +94,7 @@ public interface GradleExecuter {
     GradleExecuter withUserHomeDir(File userHomeDir);
 
     /**
-     * Sets the <em>Gradle</em> user home dir. Setting to null requests that the executer use the real default Gradle user home dir rather than the
-     * default used for testing.
+     * Sets the <em>Gradle</em> user home dir. Setting to null requests that the executer use the real default Gradle user home dir rather than the default used for testing.
      */
     GradleExecuter withGradleUserHomeDir(File userHomeDir);
 
@@ -149,10 +148,9 @@ public interface GradleExecuter {
      * Adds options that should be used to start the JVM, if a JVM is to be started. Ignored if not.
      *
      * @param gradleOpts the jvm opts
-     *
      * @return this executer
      */
-    GradleExecuter withGradleOpts(String ... gradleOpts);
+    GradleExecuter withGradleOpts(String... gradleOpts);
 
     /**
      * Sets the default character encoding to use.
@@ -184,8 +182,6 @@ public interface GradleExecuter {
     /**
      * Set the number of seconds an idle daemon should live for.
      *
-     * @param secs
-     *
      * @return this executer
      */
     GradleExecuter withDaemonIdleTimeoutSecs(int secs);
@@ -193,8 +189,6 @@ public interface GradleExecuter {
     /**
      * Set the working space for the daemon and launched daemons
      *
-     * @param baseDir
-     *
      * @return this executer
      */
     GradleExecuter withDaemonBaseDir(File baseDir);
@@ -293,4 +287,16 @@ public interface GradleExecuter {
     GradleExecuter copyTo(GradleExecuter executer);
 
     GradleExecuter withDaemonStartingMessageEnabled();
+
+    /**
+     * Where possible, starts the Gradle build process in suspended debug mode.
+     */
+    GradleExecuter withDebug(boolean flag);
+
+    /**
+     * Forces Gradle to consider the build to be interactive
+     */
+    GradleExecuter withForceInteractive(boolean flag);
+
+    boolean isDebug();
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java
index 5d2f36f..29afeb2 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java
@@ -45,4 +45,5 @@ public interface GradleHandle {
      * Returns true if the build is currently running.
      */
     boolean isRunning();
-}
\ No newline at end of file
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleVersions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleVersions.java
new file mode 100644
index 0000000..db5070c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleVersions.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Intended to be used with {@link org.gradle.integtests.tooling.fixture.TargetGradleVersion} and other similar things.
+ */
+public class GradleVersions {
+
+    private GradleVersions() {
+    }
+
+    public static final String SUPPORTS_CONTINUOUS = ">=2.5";
+    public static final String PRE_CONTINUOUS = ">=1.2 <2.5";
+
+}
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 3043cb3..4a270bf 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
@@ -28,22 +28,28 @@ 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.configuration.GradleLauncherMetaData;
 import org.gradle.execution.MultipleBuildFailures;
-import org.gradle.initialization.BuildLayoutParameters;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.initialization.GradleLauncher;
+import org.gradle.initialization.*;
 import org.gradle.internal.Factory;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.event.ListenerManager;
 import org.gradle.internal.exceptions.LocationAwareException;
+import org.gradle.internal.invocation.BuildAction;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeintegration.ProcessEnvironment;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.ServiceRegistryBuilder;
 import org.gradle.internal.service.scopes.GlobalScopeServices;
 import org.gradle.launcher.Main;
-import org.gradle.launcher.cli.converter.LayoutToPropertiesConverter;
-import org.gradle.launcher.cli.converter.PropertiesToStartParameterConverter;
+import org.gradle.launcher.cli.ExecuteBuildAction;
+import org.gradle.launcher.cli.Parameters;
+import org.gradle.launcher.cli.ParametersConverter;
+import org.gradle.launcher.daemon.configuration.DaemonUsage;
+import org.gradle.launcher.exec.BuildActionExecuter;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.initialization.ReportedException;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.ShowStacktrace;
 import org.gradle.process.internal.JavaExecHandleBuilder;
@@ -57,6 +63,7 @@ import org.hamcrest.Matchers;
 import java.io.File;
 import java.io.InputStream;
 import java.io.StringWriter;
+import java.lang.management.ManagementFactory;
 import java.nio.charset.Charset;
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -69,12 +76,13 @@ import static org.junit.Assert.*;
 
 class InProcessGradleExecuter extends AbstractGradleExecuter {
     private static final ServiceRegistry GLOBAL_SERVICES = ServiceRegistryBuilder.builder()
-            .displayName("Global services")
-            .parent(LoggingServiceRegistry.newCommandLineProcessLogging())
-            .parent(NativeServicesTestFixture.getInstance())
-            .provider(new GlobalScopeServices(true))
-            .build();
+        .displayName("Global services")
+        .parent(LoggingServiceRegistry.newCommandLineProcessLogging())
+        .parent(NativeServicesTestFixture.getInstance())
+        .provider(new GlobalScopeServices(true))
+        .build();
     private final ProcessEnvironment processEnvironment = GLOBAL_SERVICES.get(ProcessEnvironment.class);
+
     public static final TestFile COMMON_TMP = new TestFile(new File("build/tmp"));
 
     InProcessGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
@@ -89,8 +97,8 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
 
     @Override
     protected ExecutionResult doRun() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
+        StandardOutputListener outputListener = new OutputListenerImpl();
+        StandardOutputListener errorListener = new OutputListenerImpl();
         BuildListenerImpl buildListener = new BuildListenerImpl();
         BuildResult result = doRun(outputListener, errorListener, buildListener);
         try {
@@ -99,20 +107,20 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             throw new UnexpectedBuildFailure(e);
         }
         return assertResult(new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
-                new OutputScrapingExecutionResult(outputListener.toString(), errorListener.toString())));
+            new OutputScrapingExecutionResult(outputListener.toString(), errorListener.toString())));
     }
 
     @Override
     protected ExecutionFailure doRunWithFailure() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
+        StandardOutputListener outputListener = new OutputListenerImpl();
+        StandardOutputListener errorListener = new OutputListenerImpl();
         BuildListenerImpl buildListener = new BuildListenerImpl();
         try {
             doRun(outputListener, errorListener, buildListener).rethrowFailure();
             throw new AssertionError("expected build to fail but it did not.");
         } catch (GradleException e) {
             return assertResult(new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
-                    new OutputScrapingExecutionFailure(outputListener.writer.toString(), errorListener.writer.toString()), e));
+                new OutputScrapingExecutionFailure(outputListener.toString(), errorListener.toString()), e));
         }
     }
 
@@ -129,15 +137,19 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
                 builder.workingDir(getWorkingDir());
                 Set<File> classpath = new DefaultModuleRegistry().getFullClasspath();
                 builder.classpath(classpath);
+                builder.jvmArgs(getGradleOpts());
                 builder.setMain(Main.class.getName());
                 builder.args(getAllArgs());
                 builder.setStandardInput(getStdin());
+                if (isDebug()) {
+                    builder.jvmArgs(DEBUG_ARGS);
+                }
                 return builder;
             }
         }).start();
     }
 
-    private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener, BuildListenerImpl listener) {
+    private BuildResult doRun(StandardOutputListener outputListener, StandardOutputListener errorListener, BuildListenerImpl listener) {
         // Capture the current state of things that we will change during execution
         InputStream originalStdIn = System.in;
         Properties originalSysProperties = new Properties();
@@ -154,35 +166,43 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         Map<String, String> implicitJvmSystemProperties = getImplicitJvmSystemProperties();
         System.getProperties().putAll(implicitJvmSystemProperties);
 
-        StartParameter parameter = new StartParameter();
-        parameter.setCurrentDir(getWorkingDir());
-        parameter.setShowStacktrace(ShowStacktrace.ALWAYS);
+        // TODO: Fix tests that rely on this being set before we process arguments like this...
+        StartParameter startParameter = new StartParameter();
+        startParameter.setCurrentDir(getWorkingDir());
+        startParameter.setShowStacktrace(ShowStacktrace.ALWAYS);
 
         CommandLineParser parser = new CommandLineParser();
-        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
-        converter.configure(parser);
-        ParsedCommandLine parsedCommandLine = parser.parse(getAllArgs());
+        ParametersConverter parametersConverter = new ParametersConverter();
+        parametersConverter.configure(parser);
+        parametersConverter.convert(parser.parse(getAllArgs()), new Parameters(startParameter));
 
-        BuildLayoutParameters layout = converter.getLayoutConverter().convert(parsedCommandLine, new BuildLayoutParameters());
+        BuildActionExecuter<BuildActionParameters> actionExecuter = GLOBAL_SERVICES.get(BuildActionExecuter.class);
 
-        Map<String, String> properties = new HashMap<String, String>();
-        new LayoutToPropertiesConverter().convert(layout, properties);
-        converter.getSystemPropertiesConverter().convert(parsedCommandLine, properties);
+        ListenerManager listenerManager = GLOBAL_SERVICES.get(ListenerManager.class);
+        listenerManager.addListener(listener);
 
-        new PropertiesToStartParameterConverter().convert(properties, parameter);
-        converter.convert(parsedCommandLine, parameter);
-
-        DefaultGradleLauncherFactory factory = GLOBAL_SERVICES.get(DefaultGradleLauncherFactory.class);
-        factory.addListener(listener);
         try {
-            GradleLauncher gradleLauncher = factory.newInstance(parameter);
-            try {
-                gradleLauncher.addStandardOutputListener(outputListener);
-                gradleLauncher.addStandardErrorListener(errorListener);
-                return gradleLauncher.run();
-            } finally {
-                gradleLauncher.stop();
-            }
+            // TODO: Reuse more of BuildActionsFactory
+            BuildAction action = new ExecuteBuildAction(startParameter);
+            BuildActionParameters buildActionParameters = new DefaultBuildActionParameters(
+                System.getProperties(),
+                System.getenv(),
+                SystemProperties.getInstance().getCurrentDir(),
+                startParameter.getLogLevel(),
+                DaemonUsage.EXPLICITLY_DISABLED,
+                startParameter.isContinuous(),
+                interactive
+            );
+            BuildRequestContext buildRequestContext = new DefaultBuildRequestContext(
+                new DefaultBuildRequestMetaData(new GradleLauncherMetaData(),
+                    ManagementFactory.getRuntimeMXBean().getStartTime()),
+                new DefaultBuildCancellationToken(),
+                new NoOpBuildEventConsumer(),
+                outputListener, errorListener);
+            actionExecuter.execute(action, buildRequestContext, buildActionParameters);
+            return new BuildResult(null, null);
+        } catch (ReportedException e) {
+            return new BuildResult(null, e.getCause());
         } finally {
             // Restore the environment
             System.setProperties(originalSysProperties);
@@ -195,7 +215,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
                     processEnvironment.maybeRemoveEnvironmentVariable(envVar);
                 }
             }
-            factory.removeListener(listener);
+            listenerManager.removeListener(listener);
             System.setIn(originalStdIn);
         }
     }
@@ -304,6 +324,12 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             return this;
         }
 
+        @Override
+        public ExecutionResult assertOutputContains(String expectedOutput) {
+            outputResult.assertOutputContains(expectedOutput);
+            return this;
+        }
+
         public String getError() {
             return outputResult.getError();
         }
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 98dc64d..27e7dd7 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
@@ -17,6 +17,7 @@ package org.gradle.integtests.fixtures.executer;
 
 import org.apache.commons.collections.CollectionUtils;
 import org.gradle.api.Action;
+import org.gradle.util.TextUtil;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -24,9 +25,8 @@ import java.io.StringReader;
 import java.util.*;
 import java.util.regex.Pattern;
 
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.not;
+import static org.gradle.util.TextUtil.normaliseLineSeparators;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
 public class OutputScrapingExecutionResult implements ExecutionResult {
@@ -54,6 +54,12 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
         return this;
     }
 
+    @Override
+    public ExecutionResult assertOutputContains(String expectedOutput) {
+        assertThat("Substring not found in build output", TextUtil.normaliseLineSeparators(getOutput()), org.hamcrest.core.StringContains.containsString(normaliseLineSeparators(expectedOutput)));
+        return this;
+    }
+
     public String getError() {
         return error;
     }
@@ -79,27 +85,18 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
     }
 
     public ExecutionResult assertTasksSkipped(String... taskPaths) {
-        if (GradleContextualExecuter.isParallel()) {
-            return this;
-        }
         Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
         assertThat(String.format("Expected skipped tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getSkippedTasks(), equalTo(expectedTasks));
         return this;
     }
 
     public ExecutionResult assertTaskSkipped(String taskPath) {
-        if (GradleContextualExecuter.isParallel()) {
-            return this;
-        }
         Set<String> tasks = new HashSet<String>(getSkippedTasks());
         assertThat(String.format("Expected skipped task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
         return this;
     }
 
     public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
-        if (GradleContextualExecuter.isParallel()) {
-            return this;
-        }
         Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
         Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
         assertThat(String.format("Expected executed tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
@@ -113,9 +110,6 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
     }
 
     public ExecutionResult assertTaskNotSkipped(String taskPath) {
-        if (GradleContextualExecuter.isParallel()) {
-            return this;
-        }
         Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
         assertThat(String.format("Expected executed task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
         return this;
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/jvm/InstalledJvmLocator.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/jvm/InstalledJvmLocator.java
index f469a36..70755a0 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/jvm/InstalledJvmLocator.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/jvm/InstalledJvmLocator.java
@@ -21,17 +21,25 @@ import net.rubygrapefruit.platform.WindowsRegistry;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeintegration.filesystem.FileCanonicalizer;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 
 import java.io.File;
 import java.util.*;
 
 public class InstalledJvmLocator {
-    private final OperatingSystem operatingSystem = OperatingSystem.current();
-    private final WindowsRegistry windowsRegistry = NativeServicesTestFixture.getInstance().get(WindowsRegistry.class);
-    private final SystemInfo systemInfo = NativeServicesTestFixture.getInstance().get(SystemInfo.class);
-    private final FileCanonicalizer fileCanonicalizer = NativeServicesTestFixture.getInstance().get(FileCanonicalizer.class);
-    private final Jvm currentJvm = Jvm.current();
+
+    private final OperatingSystem operatingSystem;
+    private final Jvm currentJvm;
+    private final WindowsRegistry windowsRegistry;
+    private final SystemInfo systemInfo;
+    private final FileCanonicalizer fileCanonicalizer;
+
+    public InstalledJvmLocator(OperatingSystem currentOperatingSystem, Jvm currentJvm, WindowsRegistry windowsRegistry, SystemInfo systemInfo, FileCanonicalizer fileCanonicalizer) {
+        this.operatingSystem = currentOperatingSystem;
+        this.currentJvm = currentJvm;
+        this.windowsRegistry = windowsRegistry;
+        this.systemInfo = systemInfo;
+        this.fileCanonicalizer = fileCanonicalizer;
+    }
 
     /**
      * Discovers JVMs installed on the local machine. Returns the details of each JVM that can be determined efficiently, without running the JVM.
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 86c3094..dd725c9 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
@@ -68,6 +68,16 @@ public class ReleasedVersionDistributions {
         return buildContext.distribution(mostRecentFinal);
     }
 
+    public GradleDistribution getMostRecentSnapshot() {
+        String mostRecentSnapshot = getProperties().getProperty("mostRecentSnapshot");
+
+        if (mostRecentSnapshot == null) {
+            throw new RuntimeException("Unable to get the last snapshot version");
+        }
+
+        return buildContext.distribution(mostRecentSnapshot);
+    }
+
     public List<GradleDistribution> getAll() {
         if (distributions == null) {
             distributions = CollectionUtils.collect(getProperties().getProperty("versions").split("\\s+"), new Transformer<GradleDistribution, String>() {
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
index e89d910..e0d8e0c 100644
--- 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
@@ -20,9 +20,9 @@ import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.model.ModelMap
 import org.gradle.model.Mutate
 import org.gradle.model.RuleSource
-import org.gradle.model.collection.CollectionBuilder
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.TextUtil
 
@@ -133,7 +133,7 @@ class PluginBuilder {
 
             class $className extends $RuleSource.name {
                 @$Mutate.name
-                void addTask($CollectionBuilder.name<$Task.name> tasks) {
+                void addTask($ModelMap.name<$Task.name> tasks) {
                     tasks.create("fromModelRule") {
                         it.doLast {
                             println "Model rule provided task executed"
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierHttpServer.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierHttpServer.java
index e789d6e..e108d45 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierHttpServer.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierHttpServer.java
@@ -128,6 +128,7 @@ public class CyclicBarrierHttpServer extends ExternalResource {
 
                     connected = false;
                     released = false;
+                    lock.notifyAll();
                 }
 
                 System.out.println("Sending response to client");
@@ -219,4 +220,30 @@ public class CyclicBarrierHttpServer extends ExternalResource {
             release();
         }
     }
+
+    /**
+     * Blocks until the client has gone into a disconnected state
+     *
+     */
+    public void waitForDisconnect() {
+        long expiry = System.currentTimeMillis() + 20000;
+        synchronized (lock) {
+            while (released && connected && !stopped) {
+                long delay = expiry - System.currentTimeMillis();
+                if (delay <= 0) {
+                    throw new AssertionFailedError(String.format("Timeout waiting for client to disconnect from %s.", getUri()));
+                }
+                System.out.println("waiting for client to disconnect");
+                try {
+                    lock.wait(delay);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            if (stopped) {
+                throw new AssertionFailedError(String.format("Server was stopped while waiting for client to disconnect from %s.", getUri()));
+            }
+            System.out.println("client disconnected - unblocking");
+        }
+    }
 }
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 1471352..3d58276 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
@@ -371,6 +371,13 @@ class HttpServer extends ServerWithExpectations {
         expectRedirected('HEAD', path, location)
     }
 
+    /**
+     * Expects one PUT request for the given URL, responding with a redirect.
+     */
+    void expectPutRedirected(String path, String location) {
+        expectRedirected('PUT', path, location)
+    }
+
     private void expectRedirected(String method, String path, String location) {
         expect(path, false, [method], new ActionSupport("redirect to $location") {
             void handle(HttpServletRequest request, HttpServletResponse response) {
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/jvm/UbuntuJvmLocatorTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/jvm/UbuntuJvmLocatorTest.groovy
index 0707d21..fa2e23b 100644
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/jvm/UbuntuJvmLocatorTest.groovy
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/jvm/UbuntuJvmLocatorTest.groovy
@@ -18,13 +18,14 @@ package org.gradle.integtests.fixtures.jvm
 import org.gradle.api.JavaVersion
 import org.gradle.internal.nativeintegration.filesystem.FileCanonicalizer
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.testfixtures.internal.NativeServicesTestFixture
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
+import org.gradle.util.UsesNativeServices
 import org.gradle.util.VersionNumber
 import org.junit.Rule
 import spock.lang.Specification
 
+ at UsesNativeServices
 class UbuntuJvmLocatorTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
@@ -89,7 +90,6 @@ class UbuntuJvmLocatorTest extends Specification {
     def "locates JDK in canonicalized directory"() {
         given:
         jdk("real-install/java-1.7-openjdk-amd64")
-        NativeServicesTestFixture.initialize(tmpDir.testDirectory)
         libDir.file("java-1.7.0-openjdk-amd64").createLink("real-install/java-1.7-openjdk-amd64")
 
         expect:
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 f8fecd2..8845909 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
@@ -51,6 +51,14 @@ class ReleasedVersionDistributionsTest extends Specification {
         versions().mostRecentFinalRelease.version == version("1.2")
     }
 
+    def "get most recent snapshot does that"() {
+        when:
+        props.mostRecentSnapshot = "2.5-20150413220018+0000"
+
+        then:
+        versions().mostRecentSnapshot.version == version("2.5-20150413220018+0000")
+    }
+
     def "get all final does that"() {
         when:
         props.versions = "1.3-rc-1 1.2"
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy
index 7d11d67..c86bd72 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy
@@ -29,6 +29,6 @@ class BlockTarget {
 
     def getProperty(String name) {
         instants.waitFor(name)
-        return null
+        true
     }
 }
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 e5f75df..3e554b5 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
@@ -57,6 +57,8 @@ class ConcurrentSpec extends Specification {
      */
     final TestThread thread = new TestThread(instant)
 
+    final BlockTarget waitFor = new BlockTarget(instant)
+
     private final TestExecutor executor = new TestExecutor(logger)
     private final TestExecutorFactory executorFactory = new TestExecutorFactory(executor)
 
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 06a144c..d9fb26a 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
@@ -111,4 +111,10 @@ class Instants implements InstantFactory, OperationListener {
             return time
         }
     }
+
+    void assertNotReached(String name) {
+        synchronized (lock) {
+            assert timePoints[name] == null
+        }
+    }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/AbstractTestDirectoryProvider.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/AbstractTestDirectoryProvider.java
index 94b55fc..8498f52 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/AbstractTestDirectoryProvider.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/AbstractTestDirectoryProvider.java
@@ -17,6 +17,7 @@
 package org.gradle.test.fixtures.file;
 
 import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.os.OperatingSystem;
 import org.junit.rules.MethodRule;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -25,6 +26,7 @@ import org.junit.runners.model.Statement;
 
 import java.io.File;
 import java.util.Random;
+import java.util.regex.Pattern;
 
 
 /**
@@ -35,8 +37,9 @@ abstract class AbstractTestDirectoryProvider implements MethodRule, TestRule, Te
     private String prefix;
     protected static TestFile root;
     private static final Random RANDOM = new Random();
-    public static final int ALL_DIGITS_AND_LETTERS_RADIX = 36;
+    private static final int ALL_DIGITS_AND_LETTERS_RADIX = 36;
     private static final int MAX_RANDOM_PART_VALUE = Integer.valueOf("zzzzz", ALL_DIGITS_AND_LETTERS_RADIX);
+    private static final Pattern WINDOWS_RESERVED_NAMES = Pattern.compile("(con)|(prn)|(aux)|(nul)|(com\\d)|(lpt\\d)", Pattern.CASE_INSENSITIVE);
 
     private String determinePrefix() {
         StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
@@ -49,27 +52,46 @@ abstract class AbstractTestDirectoryProvider implements MethodRule, TestRule, Te
     }
 
     protected Statement doApply(final Statement base, FrameworkMethod method, Object target) {
-        init(method.getName(), target.getClass().getSimpleName());
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                getTestDirectory().maybeDeleteDir();
-                // Don't delete on failure
-            }
-        };
+        Class<?> testClass = target.getClass();
+        init(method.getName(), testClass.getSimpleName());
+        boolean leaksHandles = testClass.getAnnotation(LeaksFileHandles.class) != null || method.getAnnotation(LeaksFileHandles.class) != null;
+        return new TestDirectoryCleaningStatement(base, getTestDirectory(), leaksHandles);
     }
 
     public Statement apply(final Statement base, Description description) {
-        init(description.getMethodName(), description.getTestClass().getSimpleName());
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                getTestDirectory().deleteDir();
-                // Don't delete on failure
+        Class<?> testClass = description.getTestClass();
+        init(description.getMethodName(), testClass.getSimpleName());
+        boolean leaksHandles = testClass.getAnnotation(LeaksFileHandles.class) != null || description.getAnnotation(LeaksFileHandles.class) != null;
+        return new TestDirectoryCleaningStatement(base, getTestDirectory(), leaksHandles);
+    }
+
+    private static class TestDirectoryCleaningStatement extends Statement {
+
+        private final Statement base;
+        private final TestFile testDirectory;
+        private final boolean leaksHandles;
+
+        private TestDirectoryCleaningStatement(Statement base, TestFile testDirectory, boolean leaksHandles) {
+            this.base = base;
+            this.testDirectory = testDirectory;
+            this.leaksHandles = leaksHandles;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            base.evaluate();
+            // Don't delete on failure
+            try {
+                testDirectory.deleteDir();
+            } catch (Exception e) {
+                boolean suppressException = leaksHandles && OperatingSystem.current().isWindows();
+                if (suppressException) {
+                    e.printStackTrace();
+                } else {
+                    throw e;
+                }
             }
-        };
+        }
     }
 
     protected void init(String methodName, String className) {
@@ -95,7 +117,11 @@ abstract class AbstractTestDirectoryProvider implements MethodRule, TestRule, Te
             }
             while (true) {
                 // Use a random prefix to avoid reusing test directories
-                dir = root.file(prefix, Integer.toString(RANDOM.nextInt(MAX_RANDOM_PART_VALUE), ALL_DIGITS_AND_LETTERS_RADIX));
+                String prefix = Integer.toString(RANDOM.nextInt(MAX_RANDOM_PART_VALUE), ALL_DIGITS_AND_LETTERS_RADIX);
+                if (WINDOWS_RESERVED_NAMES.matcher(prefix).matches()) {
+                    continue;
+                }
+                dir = root.file(this.prefix, prefix);
                 if (dir.mkdirs()) {
                     break;
                 }
@@ -115,4 +141,4 @@ abstract class AbstractTestDirectoryProvider implements MethodRule, TestRule, Te
     public TestFile createDir(Object... path) {
         return file((Object[]) path).createDir();
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/LeaksFileHandles.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/LeaksFileHandles.java
new file mode 100644
index 0000000..fec39ef
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/LeaksFileHandles.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.TYPE})
+ at Inherited
+public @interface LeaksFileHandles {
+}
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 5be32bc..ef663d8 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
@@ -38,7 +38,7 @@ class TestFileHelper {
 
     void unzipTo(File target, boolean nativeTools) {
         // Check that each directory in hierarchy is present
-        file.withInputStream {InputStream instr ->
+        file.withInputStream { InputStream instr ->
             def dirs = [] as Set
             def zipStr = new ZipInputStream(instr)
             def entry
@@ -159,7 +159,7 @@ class TestFileHelper {
                 throw new RuntimeException("Could not delete '$file': $error")
             }
         } else {
-            FileUtils.deleteQuietly(file);
+            FileUtils.deleteQuietly(file)
         }
     }
 
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java
index aad2be1..e7ceada 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java
@@ -16,30 +16,69 @@
 
 package org.gradle.util;
 
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.CompositeStoppable;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
 
 /**
  * A Junit rule which restores System.in at the end of the test.
+ *
+ * Provides a pipe for providing input to System.in in the tests
  */
-public class RedirectStdIn implements MethodRule {
-    private InputStream originalStdIn;
+public class RedirectStdIn implements TestRule {
+    private PipedInputStream emulatedSystemIn = new PipedInputStream();
+    private PipedOutputStream stdinPipe;
 
-    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+    @Override
+    public Statement apply(final Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                originalStdIn = System.in;
+                final InputStream originalStdIn = System.in;
+                initPipe();
+                System.setIn(emulatedSystemIn);
                 try {
                     base.evaluate();
                 } finally {
                     System.setIn(originalStdIn);
-                    originalStdIn = null;
+                    closePipe();
                 }
             }
         };
     }
+
+    public PipedOutputStream getStdinPipe() {
+        initPipe();
+        return stdinPipe;
+    }
+
+    private void initPipe() {
+        if (stdinPipe == null) {
+            emulatedSystemIn = new PipedInputStream();
+            try {
+                stdinPipe = new PipedOutputStream(emulatedSystemIn);
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+    }
+
+    public void resetStdinPipe() {
+        closePipe();
+        initPipe();
+        System.setIn(emulatedSystemIn);
+    }
+
+    private void closePipe() {
+        CompositeStoppable.stoppable(stdinPipe, emulatedSystemIn).stop();
+        stdinPipe = null;
+        emulatedSystemIn = null;
+    }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy
index 9c9045d..0561a35 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy
@@ -15,18 +15,15 @@
  */
 package org.gradle.util
 
-import java.lang.annotation.ElementType
-import java.lang.annotation.Inherited
-import java.lang.annotation.Target
-import java.lang.annotation.RetentionPolicy
-import java.lang.annotation.Retention
-
 import org.spockframework.runtime.extension.ExtensionAnnotation
 
+import java.lang.annotation.*
+
 @Retention(RetentionPolicy.RUNTIME)
 @Target([ElementType.METHOD, ElementType.TYPE])
 @Inherited
 @ExtensionAnnotation(TestPreconditionExtension.class)
 public @interface Requires {
-    TestPrecondition[] value()
+    TestPrecondition[] value() default [TestPrecondition.NULL_REQUIREMENT]
+    Class<? extends Closure<?>> adhoc() default { true }
 }
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 c133643..9b2bfbb 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -18,7 +18,8 @@ package org.gradle.util
 import org.gradle.api.JavaVersion
 import org.gradle.internal.os.OperatingSystem
 
-enum TestPrecondition {
+enum TestPrecondition implements org.gradle.internal.Factory<Boolean> {
+    NULL_REQUIREMENT({ true }),
     SWING({
         !UNKNOWN_OS.fulfilled
     }),
@@ -124,7 +125,7 @@ enum TestPrecondition {
         FILE_PERMISSIONS.fulfilled || WINDOWS.fulfilled
     }),
     // TODO:DAZ Should be detecting this based on tool chain, not OS
-    OBJECTIVE_C_SUPPORT({
+        OBJECTIVE_C_SUPPORT({
         NOT_WINDOWS.fulfilled && NOT_UNKNOWN_OS.fulfilled
     });
 
@@ -143,5 +144,10 @@ enum TestPrecondition {
     boolean isFulfilled() {
         predicate()
     }
+
+    @Override
+    Boolean create() {
+        return isFulfilled()
+    }
 }
 
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 bbbcd69..ef44449 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,15 @@ 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 |= unsatisfied(annotation)
     }
 
     @Override
     void visitFeatureAnnotation(Requires annotation, FeatureInfo feature) {
-        feature.skipped |= annotation.value().any() { !it.fulfilled }
+        feature.skipped |= unsatisfied(annotation)
+    }
+
+    private boolean unsatisfied(Requires annotation) {
+        annotation.value().any() { !it.fulfilled } || !annotation.adhoc().newInstance(null, null).call()
     }
 }
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 fa252db..d930875 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
@@ -22,6 +22,7 @@ import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.IvyHttpModule
 import org.gradle.test.fixtures.server.http.IvyHttpRepository
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.hamcrest.Matchers
 import org.junit.Rule
 import org.mortbay.jetty.HttpStatus
@@ -29,6 +30,7 @@ import spock.lang.Unroll
 
 import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
 
+ at LeaksFileHandles
 public class IvyPublishHttpIntegTest extends AbstractIvyPublishIntegTest {
     private static final String BAD_CREDENTIALS = '''
 credentials {
@@ -365,4 +367,4 @@ credentials {
         module.jarFile.assertExists()
         module.ivyFile.assertDoesNotExist()
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpsIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpsIntegTest.groovy
index 3991579..b1d7b6a 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpsIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpsIntegTest.groovy
@@ -21,6 +21,7 @@ import org.gradle.test.fixtures.keystore.TestKeyStore
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.IvyHttpModule
 import org.gradle.test.fixtures.server.http.IvyHttpRepository
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 class IvyPublishHttpsIntegTest extends AbstractIvyPublishIntegTest {
@@ -39,6 +40,7 @@ class IvyPublishHttpsIntegTest extends AbstractIvyPublishIntegTest {
         module = ivyRemoteRepo.module('org.gradle', 'publish', '2').allowAll()
     }
 
+    @LeaksFileHandles
     def "publish with server certificate"() {
         given:
         keyStore.enableSslWithServerCert(server)
@@ -53,6 +55,7 @@ class IvyPublishHttpsIntegTest extends AbstractIvyPublishIntegTest {
         verifyPublications()
     }
 
+    @LeaksFileHandles
     def "publish with server and client certificate"() {
         given:
         keyStore.enableSslWithServerAndClientCerts(server)
@@ -67,6 +70,7 @@ class IvyPublishHttpsIntegTest extends AbstractIvyPublishIntegTest {
         verifyPublications()
     }
 
+    @LeaksFileHandles
     def "decent error message when client can't authenticate server"() {
         keyStore.enableSslWithServerCert(server)
         initBuild()
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
index 4bec820..9fffed8 100644
--- 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
@@ -25,12 +25,14 @@ import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.IvyHttpModule
 import org.gradle.test.fixtures.server.http.IvyHttpRepository
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.hamcrest.Matchers
 import org.junit.Rule
 import spock.lang.Unroll
 
 import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
 
+ at LeaksFileHandles
 public class IvyHttpPublishIntegrationTest extends AbstractIntegrationSpec {
     private static final String BAD_CREDENTIALS = '''
 credentials {
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
index 99ff107..72d4f64 100644
--- 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
@@ -70,6 +70,49 @@ uploadPublish {
         ivyDescriptor.expectArtifact("jar2").conf == ["publish"]
     }
 
+    def "publish classified artifact"() {
+        settingsFile << "rootProject.name = 'publishTest'"
+        file("file1") << "some content"
+
+        buildFile << """
+apply plugin: "base"
+
+group = "org.gradle.test"
+version = 1.9
+
+configurations { publish }
+
+task jar1(type: Jar) {
+    baseName = "jar1"
+    classifier = "classy"
+    from "file1"
+}
+
+artifacts {
+    publish jar1
+}
+
+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-classy.jar")
+
+        and:
+        def ivyDescriptor = ivyModule.parsedIvy
+        ivyDescriptor.expectArtifact("jar1").classifier == "classy"
+    }
+
     def "publish multiple artifacts in separate configurations"() {
         file("settings.gradle") << "rootProject.name = 'publishTest'"
         file("file1") << "some content"
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 9fcd514..f0ca0fd 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
@@ -41,7 +41,8 @@ public class DependencyResolverIvyPublisher implements IvyPublisher {
         IvyPublicationIdentity projectIdentity = publication.getProjectIdentity();
         ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(projectIdentity.getOrganisation(), projectIdentity.getModule(), projectIdentity.getRevision());
         ModuleVersionIdentifier moduleVersionIdentifier = DefaultModuleVersionIdentifier.newId(moduleRevisionId);
-        DefaultIvyModulePublishMetaData publishMetaData = new DefaultIvyModulePublishMetaData(moduleVersionIdentifier);
+        // TODO:DAZ This indicates the IvyPublishMetaData should probably not be responsible for creating a ModuleDescriptor...
+        DefaultIvyModulePublishMetaData publishMetaData = new DefaultIvyModulePublishMetaData(moduleVersionIdentifier, "");
 
         try {
             for (IvyArtifact publishArtifact : publication.getArtifacts()) {
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 e7c0d04..c09266b 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
@@ -37,10 +37,10 @@ import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
 import org.gradle.api.publish.plugins.PublishingPlugin;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.Path;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 
 import javax.inject.Inject;
 import java.io.File;
@@ -84,7 +84,7 @@ public class IvyPublishPlugin implements Plugin<Project> {
     static class Rules extends RuleSource {
         @Mutate
         @SuppressWarnings("UnusedDeclaration")
-        public void createTasks(CollectionBuilder<Task> tasks, PublishingExtension publishingExtension, @Path("buildDir") final File buildDir) {
+        public void createTasks(ModelMap<Task> tasks, PublishingExtension publishingExtension, @Path("buildDir") final File buildDir) {
             PublicationContainer publications = publishingExtension.getPublications();
             RepositoryHandler repositories = publishingExtension.getRepositories();
 
diff --git a/subprojects/ivy/src/testFixtures/groovy/org/gradle/api/publish/ivy/AbstractIvyRemoteLegacyPublishIntegrationTest.groovy b/subprojects/ivy/src/testFixtures/groovy/org/gradle/api/publish/ivy/AbstractIvyRemoteLegacyPublishIntegrationTest.groovy
index 9042c21..73d0122 100644
--- a/subprojects/ivy/src/testFixtures/groovy/org/gradle/api/publish/ivy/AbstractIvyRemoteLegacyPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/testFixtures/groovy/org/gradle/api/publish/ivy/AbstractIvyRemoteLegacyPublishIntegrationTest.groovy
@@ -22,8 +22,10 @@ import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
 import org.gradle.test.fixtures.ivy.RemoteIvyModule
 import org.gradle.test.fixtures.ivy.RemoteIvyRepository
 import org.gradle.test.fixtures.server.RepositoryServer
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
+ at LeaksFileHandles
 public abstract class AbstractIvyRemoteLegacyPublishIntegrationTest extends AbstractIntegrationSpec {
     abstract RepositoryServer getServer()
 
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
index 9a334fd..bbc45a2 100644
--- 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
@@ -117,10 +117,8 @@ class JacocoPlugin implements Plugin<ProjectInternal> {
     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}"))
-            }
+        config.defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("org.jacoco:org.jacoco.agent:${extension.toolVersion}"))
         }
     }
 
@@ -134,10 +132,8 @@ class JacocoPlugin implements Plugin<ProjectInternal> {
         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}"))
-            }
+        config.defaultDependencies { dependencies ->
+            dependencies.add(this.project.dependencies.create("org.jacoco:org.jacoco.ant:${extension.toolVersion}"))
         }
     }
 
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 4839fae..a35d5a7 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
@@ -24,7 +24,7 @@ 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.ResolvableDependencies
+import org.gradle.api.artifacts.DependencySet
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.plugins.javascript.base.JavaScriptExtension
 import org.gradle.plugins.javascript.rhino.RhinoExtension
@@ -56,13 +56,12 @@ class CoffeeScriptBasePlugin implements Plugin<Project> {
 
     private Configuration addJsConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, CoffeeScriptExtension extension) {
         Configuration configuration = configurations.create(CoffeeScriptExtension.JS_CONFIGURATION_NAME)
-        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
-            void execute(ResolvableDependencies resolvableDependencies) {
-                if (configuration.dependencies.empty) {
-                    String notation = "${DEFAULT_JS_DEPENDENCY_GROUP}:${DEFAULT_JS_DEPENDENCY_MODULE}:${extension.version}@js"
-                    Dependency dependency = dependencies.create(notation)
-                    configuration.dependencies.add(dependency)
-                }
+        configuration.defaultDependencies(new Action<DependencySet>() {
+            @Override
+            void execute(DependencySet configDependencies) {
+                String notation = "${DEFAULT_JS_DEPENDENCY_GROUP}:${DEFAULT_JS_DEPENDENCY_MODULE}:${extension.version}@js"
+                Dependency dependency = dependencies.create(notation)
+                configDependencies.add(dependency)
             }
         })
         configuration
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 3a8957d..b183404 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
@@ -22,7 +22,7 @@ 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.ResolvableDependencies
+import org.gradle.api.artifacts.DependencySet
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.internal.Factory
@@ -84,17 +84,14 @@ class EnvJsPlugin implements Plugin<Project> {
 
     Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, EnvJsExtension extension) {
         Configuration configuration = configurations.create(EnvJsExtension.CONFIGURATION_NAME)
-        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
-            void execute(ResolvableDependencies resolvableDependencies) {
-                if (configuration.dependencies.empty) {
-                    String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
-                    Dependency dependency = dependencies.create(notation)
-                    configuration.dependencies.add(dependency)
-                }
+        configuration.defaultDependencies(new Action<DependencySet>() {
+            @Override
+            void execute(DependencySet configDependencies) {
+                String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
+                Dependency dependency = dependencies.create(notation)
+                configDependencies.add(dependency)
             }
         })
         configuration
-
-
     }
 }
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 2c98a0c..2579573 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
@@ -22,15 +22,15 @@ 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.ResolvableDependencies
+import org.gradle.api.artifacts.DependencySet
 import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.plugins.javascript.base.JavaScriptExtension
 import org.gradle.plugins.javascript.rhino.RhinoExtension
 import org.gradle.plugins.javascript.rhino.RhinoPlugin
 
 import static org.gradle.plugins.javascript.jshint.JsHintExtension.*
-import org.gradle.api.plugins.ReportingBasePlugin
-import org.gradle.api.reporting.ReportingExtension
 
 class JsHintPlugin implements Plugin<Project> {
 
@@ -59,16 +59,14 @@ class JsHintPlugin implements Plugin<Project> {
 
     Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, JsHintExtension extension) {
         Configuration configuration = configurations.create(JsHintExtension.CONFIGURATION_NAME)
-        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
-            void execute(ResolvableDependencies resolvableDependencies) {
-                if (configuration.dependencies.empty) {
-                    String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
-                    Dependency dependency = dependencies.create(notation)
-                    configuration.dependencies.add(dependency)
-                }
+        configuration.defaultDependencies(new Action<DependencySet>() {
+            @Override
+            void execute(DependencySet configDependencies) {
+                String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
+                Dependency dependency = dependencies.create(notation)
+                configDependencies.add(dependency)
             }
         })
         configuration
-
     }
 }
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy
index cda7805..2d51c7a 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy
@@ -61,11 +61,9 @@ class RhinoPlugin implements Plugin<Project> {
     }
 
     void configureDefaultRhinoDependency(Configuration configuration, DependencyHandler dependencyHandler, RhinoExtension extension) {
-        configuration.incoming.beforeResolve {
-            if (configuration.dependencies.empty) {
-                Dependency dependency = dependencyHandler.create("${DEFAULT_RHINO_DEPENDENCY_GROUP}:${DEFAULT_RHINO_DEPENDENCY_MODULE}:${extension.version}")
-                configuration.dependencies.add(dependency)
-            }
+        configuration.defaultDependencies { dependencies ->
+            Dependency dependency = dependencyHandler.create("${DEFAULT_RHINO_DEPENDENCY_GROUP}:${DEFAULT_RHINO_DEPENDENCY_MODULE}:${extension.version}")
+            dependencies.add(dependency)
         }
     }
 
diff --git a/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java b/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
index 46f0666..1caba5a 100644
--- a/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
+++ b/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
@@ -46,12 +46,7 @@ import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class ApiGroovyCompiler implements org.gradle.language.base.internal.compile.Compiler<GroovyJavaJointCompileSpec>, Serializable {
     private final Compiler<JavaCompileSpec> javaCompiler;
@@ -169,7 +164,7 @@ public class ApiGroovyCompiler implements org.gradle.language.base.internal.comp
 
     private boolean isAnnotationProcessingDisabled(GroovyJavaJointCompileSpec spec) {
         List<String> compilerArgs = spec.getCompileOptions().getCompilerArgs();
-        return compilerArgs.contains("-proc:none");
+        return !spec.getGroovyCompileOptions().isJavaAnnotationProcessing() || compilerArgs.contains("-proc:none");
     }
 
     private boolean isAnnotationProcessorOnClasspath(ClassLoader classLoader) {
diff --git a/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompileOptions.java b/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompileOptions.java
index b87ad2f..8e16753 100644
--- a/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompileOptions.java
+++ b/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompileOptions.java
@@ -58,6 +58,8 @@ public class GroovyCompileOptions extends AbstractOptions {
 
     private File configurationScript;
 
+    private boolean javaAnnotationProcessing;
+
     /**
      * Tells whether the compilation task should fail if compile errors occurred. Defaults to {@code true}.
      */
@@ -184,6 +186,34 @@ public class GroovyCompileOptions extends AbstractOptions {
     }
 
     /**
+     * Whether the Groovy code should be subject to Java annotation processing.
+     * <p>
+     * Annotation processing of Groovy code works by having annotation processors visit the Java stubs generated by the
+     * Groovy compiler in order to support joint compilation of Groovy and Java source.
+     * <p>
+     * When set to {@code true}, stubs will be unconditionally generated for all Groovy sources, and Java annotations processors will be executed on those stubs.
+     * <p>
+     * When this option is set to {@code false} (the default), Groovy code will not be subject to annotation processing, but any joint compiled Java code will be.
+     * If the compiler argument {@code "-proc:none"} was specified as part of the Java compile options, the value of this flag will be ignored.
+     * No annotation processing will be performed regardless, on Java or Groovy source.
+     */
+    @Incubating
+    @Input
+    public boolean isJavaAnnotationProcessing() {
+        return javaAnnotationProcessing;
+    }
+
+    /**
+     * Sets whether Java annotation processors should process annotations on stubs.
+     *
+     * Defaults to {@code false}.
+     */
+    @Incubating
+    public void setJavaAnnotationProcessing(boolean javaAnnotationProcessing) {
+        this.javaAnnotationProcessing = javaAnnotationProcessing;
+    }
+
+    /**
      * Returns options for running the Groovy compiler in a separate process. These options only take effect
      * if {@code fork} is set to {@code true}.
      */
diff --git a/subprojects/language-groovy/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy b/subprojects/language-groovy/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
index c9d675b..bc9d0e3 100644
--- a/subprojects/language-groovy/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
+++ b/subprojects/language-groovy/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 package org.gradle.api.tasks.compile
 
 import org.junit.Before
@@ -40,6 +40,7 @@ class GroovyCompileOptionsTest {
         assertEquals('UTF-8', compileOptions.encoding)
         assertNotNull(compileOptions.forkOptions)
         assertNull(compileOptions.configurationScript)
+        assertFalse(compileOptions.javaAnnotationProcessing)
     }
 
     @Test public void testOptionMapForForkOptions() {
diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..a8172bc
--- /dev/null
+++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 groovy.transform.NotYetImplemented
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class JavaLanguageDependencyResolutionIntegrationTest extends AbstractIntegrationSpec {
+
+    @NotYetImplemented // dependency resolution is done, but classpath generation still fails
+    def "can resolve dependency on local library"() {
+        setup:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        dep(JvmLibrarySpec)
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'dep'
+                    }
+                }
+            }
+        }
+    }
+}
+'''
+        file('src/dep/java/Dep.java') << 'public class Dep {}'
+        file('src/main/java/TestApp.java') << 'public class TestApp extends Dep {}'
+
+        expect:
+        succeeds 'assemble'
+
+    }
+
+    @NotYetImplemented // assertion error if a dependency doesn't exist
+    def "should fail if library doesn't exist"() {
+        setup:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'someLib'
+                    }
+                }
+            }
+        }
+    }
+}
+'''
+        file('src/main/java/TestApp.java') << 'public class TestApp {}'
+
+        expect:
+        fails 'assemble'
+
+    }
+}
diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy
index 10039cf..6cfa17a 100644
--- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy
+++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy
@@ -21,10 +21,12 @@ import org.gradle.integtests.language.AbstractJvmLanguageIntegrationTest
 import org.gradle.jvm.platform.internal.DefaultJavaPlatform
 import org.gradle.language.fixtures.BadJavaComponent
 import org.gradle.language.fixtures.TestJavaComponent
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.hamcrest.Matchers
 
+ at LeaksFileHandles
 class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest {
     TestJvmComponent app = new TestJavaComponent()
 
@@ -176,4 +178,4 @@ class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest {
         failure.assertHasCause("No tool chains can provide a compiler for type DefaultJavaCompileSpec:")
         failure.assertThatCause(Matchers.containsString("Could not target platform: 'Java SE 9' using tool chain: 'JDK ${JavaVersion.current().majorVersion} (${JavaVersion.current()})'"))
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy
new file mode 100644
index 0000000..bf53e01
--- /dev/null
+++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
+
+class JavaSourceSetIntegrationTest extends AbstractIntegrationSpec {
+
+    void setup() {
+        EnableModelDsl.enable(super.executer)
+    }
+
+    def "can define dependencies on Java source set"() {
+        given:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'someLib' // Library in same project
+                        project 'otherProject' library 'someLib' // Library in other project
+                        project 'otherProject' // Library in other project, expect exactly one library
+                    }
+                }
+            }
+        }
+    }
+
+    tasks {
+        create('checkDependencies') {
+            doLast {
+                def deps = $('components.main.sources.java').dependencies
+                assert deps.size() == 3
+                assert deps[0].libraryName == 'someLib'
+                assert deps[1].projectPath == 'otherProject'
+                assert deps[1].libraryName == 'someLib'
+                assert deps[2].projectPath == 'otherProject'
+                assert deps[2].libraryName == null
+            }
+        }
+    }
+}
+'''
+        when:
+        succeeds "checkDependencies"
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "dependencies returned by the container are immutable"() {
+        given:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'someLib'
+                    }
+                }
+            }
+        }
+    }
+
+    tasks {
+        create('checkDependencies') {
+            doLast {
+                def deps = $('components.main.sources.java').dependencies
+                assert deps.size() == 1
+                assert deps[0].libraryName == 'someLib'
+                assert deps[0] instanceof org.gradle.platform.base.internal.DefaultDependencySpec // this guy is immutable
+                try {
+                    deps[0].project('foo')
+                    assert false
+                } catch (e) {
+                    // project('foo') is only available when building the dependencies
+                }
+            }
+        }
+    }
+}
+'''
+        when:
+        succeeds "checkDependencies"
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "cannot create a dependency with all null values with library"() {
+        given:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library(null)
+                    }
+                }
+            }
+        }
+    }
+
+    tasks {
+        create('checkDependencies') {
+            doLast {
+                def libraries = $('components.main.sources.java').dependencies*.libraryName
+            }
+        }
+    }
+}
+'''
+        when:
+        fails "checkDependencies"
+
+        then:
+        failure.assertHasCause('A dependency spec must have at least one of project or library name not null')
+    }
+
+    def "cannot create a dependency with all null values with project"() {
+        given:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        project(null)
+                    }
+                }
+            }
+        }
+    }
+
+    tasks {
+        create('checkDependencies') {
+            doLast {
+                def libraries = $('components.main.sources.java').dependencies*.libraryName
+            }
+        }
+    }
+}
+'''
+        when:
+        fails "checkDependencies"
+
+        then:
+        failure.assertHasCause('A dependency spec must have at least one of project or library name not null')
+    }
+
+    def "filters duplicate dependencies"() {
+        given:
+        buildFile << '''
+plugins {
+    id 'jvm-component'
+    id 'java-lang'
+}
+
+model {
+    components {
+        main(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'someLib' // Library in same project
+                        project 'otherProject' library 'someLib' // Library in other project
+                        project 'otherProject' // Library in other project, expect exactly one library
+
+                        // explicitly create duplicates
+                        library 'someLib' // Library in same project
+                        project 'otherProject' library 'someLib' // Library in other project
+                        project 'otherProject' // Library in other project, expect exactly one library
+                    }
+                }
+            }
+        }
+    }
+
+    tasks {
+        create('checkDependencies') {
+            doLast {
+                def deps = $('components.main.sources.java').dependencies
+                assert deps.size() == 3
+                assert deps[0].libraryName == 'someLib'
+                assert deps[1].projectPath == 'otherProject'
+                assert deps[1].libraryName == 'someLib'
+                assert deps[2].projectPath == 'otherProject'
+                assert deps[2].libraryName == null
+            }
+        }
+    }
+}
+'''
+        when:
+        succeeds "checkDependencies"
+
+        then:
+        noExceptionThrown()
+    }
+
+}
diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLanguageSourceSet.java b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLanguageSourceSet.java
index 62374c4..cc94b69 100644
--- a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLanguageSourceSet.java
+++ b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLanguageSourceSet.java
@@ -15,13 +15,18 @@
  */
 package org.gradle.language.java.internal;
 
+import org.gradle.api.Action;
 import org.gradle.jvm.Classpath;
+import org.gradle.language.base.internal.DependentSourceSetInternal;
 import org.gradle.language.base.sources.BaseLanguageSourceSet;
 import org.gradle.language.java.JavaSourceSet;
 import org.gradle.language.jvm.internal.EmptyClasspath;
+import org.gradle.platform.base.DependencySpecContainer;
+import org.gradle.platform.base.internal.DefaultDependencySpecContainer;
 
-public class DefaultJavaLanguageSourceSet extends BaseLanguageSourceSet implements JavaSourceSet {
+public class DefaultJavaLanguageSourceSet extends BaseLanguageSourceSet implements JavaSourceSet, DependentSourceSetInternal {
     private final Classpath compileClasspath = new EmptyClasspath();
+    private final DefaultDependencySpecContainer dependencies = new DefaultDependencySpecContainer();
 
     @Override
     protected String getTypeName() {
@@ -31,4 +36,15 @@ public class DefaultJavaLanguageSourceSet extends BaseLanguageSourceSet implemen
     public Classpath getCompileClasspath() {
         return compileClasspath;
     }
+
+    @Override
+    public DependencySpecContainer getDependencies() {
+        return dependencies;
+    }
+
+    @Override
+    public DependencySpecContainer dependencies(Action<? super DependencySpecContainer> configureAction) {
+        configureAction.execute(getDependencies());
+        return getDependencies();
+    }
 }
diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLocalComponentFactory.java b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLocalComponentFactory.java
new file mode 100644
index 0000000..93f451a
--- /dev/null
+++ b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaLocalComponentFactory.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.ComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.internal.component.local.model.*;
+import org.gradle.internal.component.model.IvyArtifactName;
+import org.gradle.internal.component.model.LocalComponentDependencyMetaData;
+import org.gradle.platform.base.DependencySpec;
+import org.gradle.platform.base.DependencySpecContainer;
+
+import java.util.Collections;
+
+public class DefaultJavaLocalComponentFactory implements LocalComponentFactory {
+
+    private static final ExcludeRule[] EXCLUDE_RULES = new ExcludeRule[0];
+
+    @Override
+    public boolean canConvert(Object source) {
+        return source instanceof DefaultJavaSourceSetResolveContext;
+    }
+
+    @Override
+    public LocalComponentMetaData convert(Object source) {
+        DefaultJavaSourceSetResolveContext context = (DefaultJavaSourceSetResolveContext) source;
+        String projectPath = context.getProject().getPath();
+        String libraryName = context.getSourceSet().getParentName();
+        String version = context.getProject().getVersion().toString();
+        ModuleVersionIdentifier id = new DefaultModuleVersionIdentifier(
+            projectPath, libraryName, version
+        );
+        ComponentIdentifier component = new DefaultLibraryComponentIdentifier(projectPath, libraryName);
+        DefaultLocalComponentMetaData metaData = new DefaultLocalComponentMetaData(id, component, Project.DEFAULT_STATUS);
+        metaData.addConfiguration(context.getName(), "Configuration for "+libraryName, Collections.<String>emptySet(), Collections.singleton(context.getName()), true, true);
+        addDependencies(projectPath, id, metaData, context.getSourceSet().getDependencies());
+        return metaData;
+    }
+
+    private void addDependencies(String defaultProject, ModuleVersionIdentifier mvi, DefaultLocalComponentMetaData metaData, DependencySpecContainer allDependencies) {
+        for (DependencySpec dependency : allDependencies) {
+            ComponentSelector selector;
+            String projectPath = dependency.getProjectPath();
+            if (projectPath==null) {
+                projectPath = defaultProject;
+            }
+            if (dependency.getLibraryName()==null) {
+                selector = new DefaultProjectComponentSelector(dependency.getProjectPath());
+            } else {
+                selector = new DefaultLibraryComponentSelector(projectPath, dependency.getLibraryName());
+            }
+            DefaultModuleVersionSelector requested = new DefaultModuleVersionSelector(projectPath, dependency.getLibraryName(), mvi.getVersion());
+            LocalComponentDependencyMetaData localComponentDependencyMetaData = new LocalComponentDependencyMetaData(
+                selector,
+                requested,
+                //"*", "*",
+                DefaultLibraryComponentIdentifier.libraryToConfigurationName(mvi.getGroup(), mvi.getName()),
+                DefaultLibraryComponentIdentifier.libraryToConfigurationName(projectPath, dependency.getLibraryName()),
+                Collections.<IvyArtifactName>emptySet(),
+                EXCLUDE_RULES,
+                false,
+                false,
+                true);
+            metaData.addDependency(localComponentDependencyMetaData);
+        }
+    }
+
+}
diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContext.java b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContext.java
new file mode 100644
index 0000000..3e7e3f1
--- /dev/null
+++ b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContext.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.artifacts.DefaultDependencySet;
+import org.gradle.api.internal.artifacts.ResolveContextInternal;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.component.local.model.DefaultLibraryComponentIdentifier;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
+
+public class DefaultJavaSourceSetResolveContext implements ResolveContextInternal {
+    private final Project project;
+    private final DefaultJavaLanguageSourceSet sourceSet;
+
+    public DefaultJavaSourceSetResolveContext(Project project, DefaultJavaLanguageSourceSet sourceSet) {
+        this.project = project;
+        this.sourceSet = sourceSet;
+    }
+
+    @Override
+    public String getName() {
+        return DefaultLibraryComponentIdentifier.libraryToConfigurationName(project.getPath(), getLibraryName());
+    }
+
+    private String getLibraryName() {
+        return sourceSet.getParentName();
+    }
+
+    @Override
+    public DependencySet getDependencies() {
+        DefaultDomainObjectSet<Dependency> backingSet = new DefaultDomainObjectSet<Dependency>(Dependency.class);
+        return new DefaultDependencySet(getLibraryName(), backingSet);
+    }
+
+
+    @Override
+    public DependencySet getAllDependencies() {
+        return new DefaultDependencySet(getLibraryName(), new DefaultDomainObjectSet<Dependency>(Dependency.class));
+    }
+
+    public DefaultJavaLanguageSourceSet getSourceSet() {
+        return sourceSet;
+    }
+
+    public Project getProject() {
+        return project;
+    }
+
+    @Override
+    public ProjectDependencyResolver newProjectDependencyResolver(final ProjectComponentRegistry projectComponentRegistry, final LocalComponentFactory localComponentFactory, DependencyToComponentIdResolver delegateIdResolver, ComponentMetaDataResolver delegateComponentResolver) {
+        return new ProjectLibraryDependencyResolver((ProjectInternal) getProject(), projectComponentRegistry, localComponentFactory, delegateIdResolver, delegateComponentResolver);
+    }
+
+}
diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/ProjectLibraryDependencyResolver.java b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/ProjectLibraryDependencyResolver.java
new file mode 100644
index 0000000..266a98c
--- /dev/null
+++ b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/ProjectLibraryDependencyResolver.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.component.LibraryComponentSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.LocalComponentFactory;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectComponentRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.component.local.model.DefaultLibraryComponentIdentifier;
+import org.gradle.internal.component.local.model.DefaultLocalComponentMetaData;
+import org.gradle.internal.component.model.DependencyMetaData;
+import org.gradle.internal.resolve.ModuleVersionResolveException;
+import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
+import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver;
+import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.platform.base.ComponentSpecContainer;
+import org.gradle.platform.base.LibrarySpec;
+
+import java.util.Collections;
+
+// TODO: this should probably be in the platform-base module somehow, but it doesn't depend on dependency-resolution
+// module so it's not possible yet
+public class ProjectLibraryDependencyResolver extends ProjectDependencyResolver {
+    private final ProjectInternal defaultProject;
+
+    public ProjectLibraryDependencyResolver(ProjectInternal project, ProjectComponentRegistry projectComponentRegistry, LocalComponentFactory localComponentFactory, DependencyToComponentIdResolver delegateIdResolver, ComponentMetaDataResolver delegateComponentResolver) {
+        super(projectComponentRegistry, localComponentFactory, delegateIdResolver, delegateComponentResolver);
+        defaultProject = project;
+    }
+
+    @Override
+    public void resolve(DependencyMetaData dependency, BuildableComponentIdResolveResult result) {
+        if (dependency.getSelector() instanceof LibraryComponentSelector) {
+            DefaultLocalComponentMetaData metaData = null;
+            LibraryComponentSelector selector = (LibraryComponentSelector) dependency.getSelector();
+            ProjectInternal project = defaultProject;
+            if (selector.getProjectPath()!=null) {
+                project = project.getRootProject().findProject(selector.getProjectPath());
+            }
+            if (project!=null) {
+                ComponentSpecContainer components = project.getModelRegistry().realize(
+                    ModelPath.path("components"),
+                    ModelType.of(ComponentSpecContainer.class));
+                ModelMap<? extends LibrarySpec> libraries = components.withType(LibrarySpec.class);
+                String libraryName = selector.getLibraryName();
+                if (libraryName==null && libraries.size()==1) {
+                    libraryName = libraries.values().iterator().next().getName();
+                }
+                if (libraryName!=null) {
+                    String version = project.getVersion().toString();
+                    String projectPath = project.getPath();
+                    LibrarySpec library = libraries.get(libraryName);
+                    if (library != null) {
+                        ModuleVersionIdentifier id = new DefaultModuleVersionIdentifier(
+                            projectPath, libraryName, version
+                        );
+                        ComponentIdentifier component = new DefaultLibraryComponentIdentifier(projectPath, library.getName());
+                        metaData = new DefaultLocalComponentMetaData(id, component, Project.DEFAULT_STATUS);
+                        metaData.addConfiguration(DefaultLibraryComponentIdentifier.libraryToConfigurationName(projectPath, libraryName), "Configuration for "+libraryName, Collections.<String>emptySet(), Collections.singleton(DefaultLibraryComponentIdentifier.libraryToConfigurationName(projectPath, libraryName)), true, true);
+                    }
+                }
+            }
+            if (metaData!=null) {
+                result.resolved(metaData.toResolveMetaData());
+            } else {
+                result.failed(new ModuleVersionResolveException(selector, String.format("Cannot resolve dependency %s", selector)));
+            }
+        } else {
+            super.resolve(dependency, result);
+        }
+    }
+
+}
diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/plugins/JavaLanguagePlugin.java b/subprojects/language-java/src/main/java/org/gradle/language/java/plugins/JavaLanguagePlugin.java
index d1e5fba..60bb233 100644
--- a/subprojects/language-java/src/main/java/org/gradle/language/java/plugins/JavaLanguagePlugin.java
+++ b/subprojects/language-java/src/main/java/org/gradle/language/java/plugins/JavaLanguagePlugin.java
@@ -16,10 +16,23 @@
 
 package org.gradle.language.java.plugins;
 
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.component.ComponentIdentifier;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+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.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.GlobalDependencyResolutionRules;
+import org.gradle.api.internal.artifacts.ResolverResults;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.CompositeResolveLocalComponentFactory;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.jvm.JvmBinarySpec;
 import org.gradle.jvm.JvmByteCode;
@@ -30,6 +43,8 @@ import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.language.java.JavaSourceSet;
 import org.gradle.language.java.internal.DefaultJavaLanguageSourceSet;
+import org.gradle.language.java.internal.DefaultJavaLocalComponentFactory;
+import org.gradle.language.java.internal.DefaultJavaSourceSetResolveContext;
 import org.gradle.language.java.tasks.PlatformJavaCompile;
 import org.gradle.language.jvm.plugins.JvmResourcesPlugin;
 import org.gradle.model.Mutate;
@@ -39,8 +54,7 @@ import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
 
 import java.io.File;
-import java.util.Collections;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Plugin for compiling Java code. Applies the {@link org.gradle.language.base.plugins.ComponentModelBasePlugin} and {@link org.gradle.language.jvm.plugins.JvmResourcesPlugin}. Registers "java"
@@ -51,6 +65,14 @@ public class JavaLanguagePlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPluginManager().apply(ComponentModelBasePlugin.class);
         project.getPluginManager().apply(JvmResourcesPlugin.class);
+        GradleInternal gradle = (GradleInternal) project.getGradle();
+        registerLocalComponentFactory(gradle);
+    }
+
+    private void registerLocalComponentFactory(GradleInternal gradle) {
+        ServiceRegistry services = gradle.getServices();
+        CompositeResolveLocalComponentFactory componentFactory = services.get(CompositeResolveLocalComponentFactory.class);
+        componentFactory.addFactory(new DefaultJavaLocalComponentFactory());
     }
 
     @SuppressWarnings("UnusedDeclaration")
@@ -95,12 +117,20 @@ public class JavaLanguagePlugin implements Plugin<Project> {
                     JavaSourceSet javaSourceSet = (JavaSourceSet) sourceSet;
                     JvmBinarySpec binary = (JvmBinarySpec) binarySpec;
 
+                    // TODO: Probably need to extract this in a utility class for language plugins,
+                    // or a language plugin superclass in order to avoid the use of internal APIs
+                    GradleInternal gradle = (GradleInternal) task.getProject().getGradle();
+                    ArtifactDependencyResolver dependencyResolver = gradle.getServices().get(ArtifactDependencyResolver.class);
+                    ProjectInternal project = (ProjectInternal) task.getProject();
+                    RepositoryHandler repositories = project.getRepositories();
+                    GlobalDependencyResolutionRules globalDependencyResolutionRules = project.getServices().get(GlobalDependencyResolutionRules.class);
+
                     compile.setDescription(String.format("Compiles %s.", javaSourceSet));
                     compile.setDestinationDir(binary.getClassesDir());
                     compile.setPlatform(binary.getTargetPlatform());
 
                     compile.setSource(javaSourceSet.getSource());
-                    compile.setClasspath(javaSourceSet.getCompileClasspath().getFiles());
+                    compile.setClasspath(new DependencyResolvingClasspath(project, javaSourceSet, dependencyResolver, repositories, globalDependencyResolutionRules));
                     compile.setTargetCompatibility(binary.getTargetPlatform().getTargetCompatibility().toString());
                     compile.setSourceCompatibility(binary.getTargetPlatform().getTargetCompatibility().toString());
 
@@ -115,4 +145,65 @@ public class JavaLanguagePlugin implements Plugin<Project> {
             return binary instanceof JvmBinarySpec;
         }
     }
+
+    private static class DependencyResolvingClasspath extends AbstractFileCollection {
+        private final Project project;
+        private final JavaSourceSet sourceSet;
+        private final ArtifactDependencyResolver dependencyResolver;
+        private final GlobalDependencyResolutionRules globalDependencyResolutionRules;
+        private final RepositoryHandler repositories;
+
+        private DependencyResolvingClasspath(
+            Project project,
+            JavaSourceSet sourceSet,
+            ArtifactDependencyResolver dependencyResolver,
+            RepositoryHandler repositories,
+            GlobalDependencyResolutionRules globalDependencyResolutionRules) {
+            this.project = project;
+            this.sourceSet = sourceSet;
+            this.dependencyResolver = dependencyResolver;
+            this.repositories = repositories;
+            this.globalDependencyResolutionRules = globalDependencyResolutionRules;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return "Classpath for "+sourceSet.getDisplayName();
+        }
+
+        @Override
+        public Set<File> getFiles() {
+            final Set<File> classpath = new LinkedHashSet<File>();
+            classpath.addAll(sourceSet.getCompileClasspath().getFiles().getFiles());
+            ResolverResults results = new ResolverResults();
+            final List<ResolutionAwareRepository> resolutionRepositories = getResolutionAwareRepositories();
+            DefaultJavaSourceSetResolveContext resolveContext = new DefaultJavaSourceSetResolveContext(project, (DefaultJavaLanguageSourceSet) sourceSet);
+            dependencyResolver.resolve(resolveContext, resolutionRepositories, globalDependencyResolutionRules, results);
+            ResolutionResult resolutionResult = results.getResolutionResult();
+            resolutionResult.allDependencies(new Action<DependencyResult>() {
+                @Override
+                public void execute(DependencyResult dependencyResult) {
+                    if (dependencyResult instanceof ResolvedDependencyResult) {
+                        ResolvedDependencyResult resolved = (ResolvedDependencyResult) dependencyResult;
+                        ResolvedComponentResult selected = resolved.getSelected();
+                        ComponentIdentifier id = selected.getId();
+                        // TODO: Convert this into actual classpath!
+                        // System.out.println("selected = " + selected);
+                    }
+                }
+            });
+            return classpath;
+        }
+
+        private List<ResolutionAwareRepository> getResolutionAwareRepositories() {
+            ImmutableList<ArtifactRepository> artifactRepositories = ImmutableList.copyOf(repositories.iterator());
+            List<ResolutionAwareRepository> resolutionRepositories = new LinkedList<ResolutionAwareRepository>();
+            for (ArtifactRepository artifactRepository : artifactRepositories) {
+                if (artifactRepository instanceof ResolutionAwareRepository) {
+                    resolutionRepositories.add((ResolutionAwareRepository)artifactRepository);
+                }
+            }
+            return resolutionRepositories;
+        }
+    }
 }
diff --git a/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLanguageSourceSetTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLanguageSourceSetTest.groovy
new file mode 100644
index 0000000..c3d83f8
--- /dev/null
+++ b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLanguageSourceSetTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.sources.BaseLanguageSourceSet
+import spock.lang.Specification
+
+class DefaultJavaLanguageSourceSetTest extends Specification {
+
+    Instantiator instantiator
+
+    def setup() {
+        instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), DirectInstantiator.INSTANCE)
+    }
+
+    def "can add a project dependency using dependencies property"() {
+        def sourceSet = newJavaSourceSet()
+
+        when:
+        sourceSet.dependencies.project ':foo'
+
+        then:
+        sourceSet.dependencies.size() == 1
+        sourceSet.dependencies[0].projectPath == ':foo'
+    }
+
+    def "can add a project dependency"() {
+        def sourceSet = newJavaSourceSet()
+
+        when:
+        sourceSet.dependencies {
+            project ':foo'
+        }
+
+        then:
+        sourceSet.dependencies.size() == 1
+        sourceSet.dependencies[0].projectPath == ':foo'
+    }
+
+    def "can add a library dependency"() {
+        def sourceSet = newJavaSourceSet()
+
+        when:
+        sourceSet.dependencies {
+            library 'fooLib'
+        }
+
+        then:
+        sourceSet.dependencies.size() == 1
+        sourceSet.dependencies[0].libraryName == 'fooLib'
+    }
+
+    def "can add a project library dependency"() {
+        def sourceSet = newJavaSourceSet()
+
+        when:
+        sourceSet.dependencies {
+            project ':foo' library 'fooLib'
+        }
+
+        then:
+        sourceSet.dependencies.size() == 1
+        sourceSet.dependencies[0].projectPath == ':foo'
+        sourceSet.dependencies[0].libraryName == 'fooLib'
+    }
+
+    def "can add a multiple dependencies"() {
+        def sourceSet = newJavaSourceSet()
+
+        when:
+        sourceSet.dependencies {
+            project ':foo'
+            library 'fooLib'
+            project ':bar' library 'barLib'
+        }
+
+        then:
+        sourceSet.dependencies.size() == 3
+        sourceSet.dependencies[0].projectPath == ':foo'
+        sourceSet.dependencies[1].libraryName == 'fooLib'
+        sourceSet.dependencies[2].projectPath == ':bar'
+        sourceSet.dependencies[2].libraryName == 'barLib'
+    }
+
+    private DefaultJavaLanguageSourceSet newJavaSourceSet() {
+        BaseLanguageSourceSet.create(DefaultJavaLanguageSourceSet, "javaX", "javaX", Stub(FileResolver), instantiator)
+    }
+}
diff --git a/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLocalComponentFactoryTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLocalComponentFactoryTest.groovy
new file mode 100644
index 0000000..7bcd15b
--- /dev/null
+++ b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaLocalComponentFactoryTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.component.LibraryComponentIdentifier
+import org.gradle.internal.component.model.ComponentResolveMetaData
+import org.gradle.platform.base.DependencySpecContainer
+import org.gradle.platform.base.internal.DefaultDependencySpec
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DefaultJavaLocalComponentFactoryTest extends Specification {
+    def "can convert java source set resolve context"() {
+        given:
+        def context = new DefaultJavaSourceSetResolveContext(Mock(Project), Mock(DefaultJavaLanguageSourceSet))
+
+        when:
+        def factory = new DefaultJavaLocalComponentFactory()
+
+        then:
+        factory.canConvert(context)
+    }
+
+    def "can convert a simple java component"() {
+        given: "a java sourceset that doesn't define any dependency"
+        def sourceSet = Mock(DefaultJavaLanguageSourceSet)
+        def dependencySpecs = Mock(DependencySpecContainer)
+        def project = Mock(Project)
+
+        dependencySpecs.iterator() >> { [].iterator() }
+        sourceSet.parentName >> 'myLib'
+        sourceSet.dependencies >> dependencySpecs
+        project.path >> ':myPath'
+        project.version >> '1.0'
+
+        def context = new DefaultJavaSourceSetResolveContext(project, sourceSet)
+
+        when: "we create a local component factory"
+        def factory = new DefaultJavaLocalComponentFactory()
+
+        then: "the factory can convert the resolve context"
+        factory.canConvert(context)
+
+        when: "we convert the context to a local component"
+        def component = factory.convert(context)
+
+        then: "component metadata reflects the library configuration"
+        component.id instanceof ModuleVersionIdentifier
+        component.id.group == ':myPath'
+        component.id.name == 'myLib'
+        component.id.version == '1.0'
+        component.id.toString() == ':myPath:myLib:1.0'
+
+        when: "we create resolution metadata"
+        def metadata = component.toResolveMetaData()
+
+        then: "metadata reflects the appropriate library information"
+        metadata instanceof ComponentResolveMetaData
+        metadata.componentId instanceof LibraryComponentIdentifier
+        metadata.componentId.displayName == 'project :myPath library myLib'
+        metadata.dependencies.empty
+        !metadata.changing
+        metadata.configurationNames == ['project :myPath library myLib'] as Set
+        metadata.source == null
+    }
+
+    @Unroll
+    def "can convert a java component with #dependenciesDescriptor"() {
+        given: "a java sourceset that defines dependencies"
+        def sourceSet = Mock(DefaultJavaLanguageSourceSet)
+        def dependencySpecs = Mock(DependencySpecContainer)
+        def project = Mock(Project)
+
+        dependencySpecs.iterator() >> { dependencies.iterator() }
+        sourceSet.parentName >> 'myLib'
+        sourceSet.dependencies >> dependencySpecs
+        project.path >> ':myPath'
+        project.version >> '1.0'
+
+        def context = new DefaultJavaSourceSetResolveContext(project, sourceSet)
+
+        when: "we create a local component factory"
+        def factory = new DefaultJavaLocalComponentFactory()
+
+        then: "the factory can convert the resolve context"
+        factory.canConvert(context)
+
+        when: "we convert the context to a local component"
+        def component = factory.convert(context)
+
+        then: "component metadata reflects the library configuration"
+        component.id instanceof ModuleVersionIdentifier
+        component.id.group == ':myPath'
+        component.id.name == 'myLib'
+        component.id.version == '1.0'
+        component.id.toString() == ':myPath:myLib:1.0'
+
+        when: "we create resolution metadata"
+        def metadata = component.toResolveMetaData()
+
+        then: "metadata reflects the appropriate library information"
+        metadata instanceof ComponentResolveMetaData
+        metadata.componentId instanceof LibraryComponentIdentifier
+        metadata.componentId.displayName == 'project :myPath library myLib'
+        !metadata.changing
+        metadata.configurationNames == ['project :myPath library myLib'] as Set
+        metadata.source == null
+
+        and: "component metadata dependencies correspond to the defined dependencies"
+        metadata.dependencies.size() == dependencies.size()
+        dependencies.eachWithIndex { spec, i ->
+            def componentDep = metadata.dependencies[i]
+            assert componentDep.requested.group == spec.projectPath?:project.path
+            assert componentDep.requested.name == spec.libraryName
+            assert componentDep.requested.version == project.version
+        }
+
+        where:
+        dependencies                                                                                        | dependenciesDescriptor
+        [new DefaultDependencySpec('someLib', ':myPath')]                                                   | 'single dependency with explicit project'
+        [new DefaultDependencySpec('someLib', ':myPath'), new DefaultDependencySpec('someLib2', ':myPath')] | '2 deps on the same project'
+        [new DefaultDependencySpec('someLib', ':myPath'), new DefaultDependencySpec('someLib', ':myPath2')] | '2 deps on 2 different projects'
+        [new DefaultDependencySpec('someLib', null)]                                                        | 'a single dependency on the current project'
+
+    }
+}
diff --git a/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContextTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContextTest.groovy
new file mode 100644
index 0000000..e7b74b3
--- /dev/null
+++ b/subprojects/language-java/src/test/groovy/org/gradle/language/java/internal/DefaultJavaSourceSetResolveContextTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DefaultJavaSourceSetResolveContextTest extends Specification {
+    def "resolve context can be created from a java source set"() {
+        given:
+        def project = Mock(Project)
+        def sourceset = Mock(DefaultJavaLanguageSourceSet)
+
+        when:
+        def context = new DefaultJavaSourceSetResolveContext(project, sourceset)
+
+        then:
+        context.project == project
+        context.dependencies.empty
+        context.allDependencies.empty
+    }
+
+    @Unroll
+    def "context name for project #path and library #library is #contextName"() {
+        given:
+        def project = Mock(Project)
+        def sourceset = Mock(DefaultJavaLanguageSourceSet)
+
+        when:
+        project.path >> path
+        sourceset.parentName >> library
+        def context = new DefaultJavaSourceSetResolveContext(project, sourceset)
+
+        then:
+        context.name == contextName
+
+        where:
+        path       | library  | contextName
+        ':myPath'  | 'myLib'  | 'project :myPath library myLib'
+        ':myPath'  | 'myLib2' | 'project :myPath library myLib2'
+        ':myPath2' | 'myLib'  | 'project :myPath2 library myLib'
+    }
+}
diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/TestJavaComponent.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/TestJavaComponent.groovy
index 6b248bd..a0ef8a4 100644
--- a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/TestJavaComponent.groovy
+++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/TestJavaComponent.groovy
@@ -69,4 +69,9 @@ interface Extra {
             resources[0],
             resources[1]
     ]
+
+    @Override
+    TestFile createIgnoredFileInSources(TestFile sourceDir) {
+        sourceDir.createFile("java/SomeIgnoredFile.java~") << '// this file should be ignored'
+    }
 }
diff --git a/subprojects/language-jvm/src/integTest/groovy/org/gradle/language/jvm/ResourceOnlyJvmLibraryIntegrationTest.groovy b/subprojects/language-jvm/src/integTest/groovy/org/gradle/language/jvm/ResourceOnlyJvmLibraryIntegrationTest.groovy
index bcba3b2..0536ad9 100644
--- a/subprojects/language-jvm/src/integTest/groovy/org/gradle/language/jvm/ResourceOnlyJvmLibraryIntegrationTest.groovy
+++ b/subprojects/language-jvm/src/integTest/groovy/org/gradle/language/jvm/ResourceOnlyJvmLibraryIntegrationTest.groovy
@@ -17,11 +17,17 @@
 package org.gradle.language.jvm
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.test.fixtures.archive.JarTestFixture
 
 class ResourceOnlyJvmLibraryIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
     def "can define a library containing resources only"() {
-        buildFile << """
+        buildFile << '''
 plugins {
     id 'jvm-component'
     id 'jvm-resources'
@@ -30,22 +36,26 @@ model {
     components {
         myLib(JvmLibrarySpec)
     }
-}
+    tasks {
+        create("validate") {
+            def components = $("components")
+            doLast {
+                def myLib = components.myLib
+                assert myLib instanceof JvmLibrarySpec
 
-task validate << {
-    def myLib = componentSpecs.myLib
-    assert myLib instanceof JvmLibrarySpec
+                assert myLib.sources.size() == 1
+                assert myLib.sources.resources instanceof JvmResourceSet
 
-    assert myLib.sources.size() == 1
-    assert myLib.sources.resources instanceof JvmResourceSet
+                assert project.sources as Set == myLib.sources as Set
 
-    assert sources as Set == myLib.sources as Set
-
-    binaries.withType(JarBinarySpec) { jvmBinary ->
-        assert jvmBinary.source == myLib.source
+                project.binaries.withType(JarBinarySpec) { jvmBinary ->
+                    assert jvmBinary.source.toList() == myLib.source.values().toList()
+                }
+            }
+        }
     }
 }
-"""
+'''
 
         expect:
         run 'validate'
diff --git a/subprojects/language-jvm/src/main/java/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java b/subprojects/language-jvm/src/main/java/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java
index afe6f2f..ce60bc9 100644
--- a/subprojects/language-jvm/src/main/java/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java
+++ b/subprojects/language-jvm/src/main/java/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonStarter.java
@@ -43,6 +43,7 @@ public class CompilerDaemonStarter {
         builder.setLogLevel(startParameter.getLogLevel()); // NOTE: might make sense to respect per-compile-task log level
         builder.applicationClasspath(forkOptions.getClasspath());
         builder.sharedPackages(forkOptions.getSharedPackages());
+        builder.setLoadApplicationInSystemClassLoader(true);
         JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
         javaCommand.setMinHeapSize(forkOptions.getMinHeapSize());
         javaCommand.setMaxHeapSize(forkOptions.getMaxHeapSize());
diff --git a/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/fixtures/jvm/IncrementalTestJvmComponent.groovy b/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/fixtures/jvm/IncrementalTestJvmComponent.groovy
index 86f5f06..2249d56 100644
--- a/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/fixtures/jvm/IncrementalTestJvmComponent.groovy
+++ b/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/fixtures/jvm/IncrementalTestJvmComponent.groovy
@@ -23,4 +23,6 @@ abstract class IncrementalTestJvmComponent extends TestJvmComponent {
     abstract void changeSources(List<TestFile> testFiles)
 
     abstract void writeAdditionalSources(TestFile testFile)
+
+    abstract TestFile createIgnoredFileInSources(TestFile sourceDir)
 }
diff --git a/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/language/AbstractJvmPluginLanguageIntegrationTest.groovy b/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/language/AbstractJvmPluginLanguageIntegrationTest.groovy
index bb5d051..eb19a5a 100644
--- a/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/language/AbstractJvmPluginLanguageIntegrationTest.groovy
+++ b/subprojects/language-jvm/src/testFixtures/groovy/org/gradle/integtests/language/AbstractJvmPluginLanguageIntegrationTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.integtests.language
 
 import com.sun.xml.internal.ws.util.StringUtils
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.test.fixtures.archive.JarTestFixture
 import org.gradle.util.TextUtil
 
@@ -35,7 +36,9 @@ abstract class AbstractJvmPluginLanguageIntegrationTest extends AbstractIntegrat
         throw new UnsupportedOperationException("Cannot determine language name from class name '${getClass().simpleName}.")
     }
 
-    def setup(){
+    def setup() {
+        EnableModelDsl.enable(executer)
+
         buildFile << """
         plugins {
             id 'jvm-component'
@@ -51,20 +54,24 @@ abstract class AbstractJvmPluginLanguageIntegrationTest extends AbstractIntegrat
         components {
             myLib(JvmLibrarySpec)
         }
-    }
-
-    task validate << {
-        def myLib = componentSpecs.myLib
-        assert myLib instanceof JvmLibrarySpec
-
-        assert myLib.sources.size() == 2
-        assert myLib.sources.${languageName} instanceof ${sourceSetTypeName}
-        assert myLib.sources.resources instanceof JvmResourceSet
-
-        assert sources as Set == myLib.sources as Set
-
-        binaries.withType(JarBinarySpec) { jvmBinary ->
-            assert jvmBinary.source == myLib.source
+        tasks {
+            create("validate") {
+                def components = \$("components")
+                doLast {
+                    def myLib = components.myLib
+                    assert myLib instanceof JvmLibrarySpec
+
+                    assert myLib.sources.size() == 2
+                    assert myLib.sources.${languageName} instanceof ${sourceSetTypeName}
+                    assert myLib.sources.resources instanceof JvmResourceSet
+
+                    assert project.sources as Set == myLib.sources as Set
+
+                    project.binaries.withType(JarBinarySpec) { jvmBinary ->
+                        assert jvmBinary.source.toList() == myLib.source.values().toList()
+                    }
+                }
+            }
         }
     }
 """
@@ -87,22 +94,26 @@ abstract class AbstractJvmPluginLanguageIntegrationTest extends AbstractIntegrat
                 }
             }
         }
-    }
-
-    task validate << {
-        def myLib = componentSpecs.myLib
-        assert myLib instanceof JvmLibrarySpec
-
-        assert myLib.sources.size() == 4
-        assert myLib.sources.${languageName} instanceof ${sourceSetTypeName}
-        assert myLib.sources.extra${languageName} instanceof ${sourceSetTypeName}
-        assert myLib.sources.resources instanceof JvmResourceSet
-        assert myLib.sources.extraResources instanceof JvmResourceSet
-
-        assert sources as Set == myLib.sources as Set
-
-        binaries.withType(JarBinarySpec) { jvmBinary ->
-            assert jvmBinary.source == myLib.source
+        tasks {
+            create("validate") {
+                def components = \$("components")
+                doLast {
+                    def myLib = components.myLib
+                    assert myLib instanceof JvmLibrarySpec
+
+                    assert myLib.sources.size() == 4
+                    assert myLib.sources.${languageName} instanceof ${sourceSetTypeName}
+                    assert myLib.sources.extra${languageName} instanceof ${sourceSetTypeName}
+                    assert myLib.sources.resources instanceof JvmResourceSet
+                    assert myLib.sources.extraResources instanceof JvmResourceSet
+
+                    assert project.sources as Set == myLib.sources as Set
+
+                    project.binaries.withType(JarBinarySpec) { jvmBinary ->
+                        assert jvmBinary.source.toList() == myLib.source.values().toList()
+                    }
+                }
+            }
         }
     }
 """
@@ -156,19 +167,19 @@ abstract class AbstractJvmPluginLanguageIntegrationTest extends AbstractIntegrat
         and:
         output.contains(TextUtil.toPlatformLineSeparators("""
     JVM resources 'myLib:extraResources'
-        src${File.separator}myLib${File.separator}extraResources"""))
+        srcDir: src${File.separator}myLib${File.separator}extraResources"""))
 
         output.contains(TextUtil.toPlatformLineSeparators("""
     ${StringUtils.capitalize(languageName)} source 'myLib:extra${languageName}'
-        src${File.separator}myLib${File.separator}extra${languageName}"""))
+        srcDir: src${File.separator}myLib${File.separator}extra${languageName}"""))
 
         output.contains(TextUtil.toPlatformLineSeparators("""
     JVM resources 'myLib:resources'
-        src${File.separator}myLib${File.separator}resources"""))
+        srcDir: src${File.separator}myLib${File.separator}resources"""))
 
         output.contains(TextUtil.toPlatformLineSeparators("""
     ${StringUtils.capitalize(languageName)} source 'myLib:${languageName}'
-        src${File.separator}myLib${File.separator}${languageName}"""))
+        srcDir: src${File.separator}myLib${File.separator}${languageName}"""))
     }
 
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalBuildIntegrationTest.groovy
index a74c9c8..2bff6df 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalBuildIntegrationTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.language
 
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.AvailableToolChains
@@ -43,6 +42,7 @@ abstract class AbstractNativeLanguageIncrementalBuildIntegrationTest extends Abs
     String libraryCompileTask
     TestFile sourceFile
     TestFile headerFile
+    TestFile commonHeaderFile
     List<TestFile> librarySourceFiles = []
 
     boolean isCanBuildForMultiplePlatforms() {
@@ -86,12 +86,12 @@ abstract class AbstractNativeLanguageIncrementalBuildIntegrationTest extends Abs
         settingsFile << "rootProject.name = 'test'"
         sourceFile = app.mainSource.writeToDir(file("src/main"))
         headerFile = app.libraryHeader.writeToDir(file("src/hello"))
+        commonHeaderFile = app.commonHeader.writeToDir(file("src/hello"))
         app.librarySources.each {
             librarySourceFiles << it.writeToDir(file("src/hello"))
         }
     }
 
-    @IgnoreIf({GradleContextualExecuter.parallel})
     def "does not re-execute build with no change"() {
         given:
         run "installMainExecutable"
@@ -103,7 +103,7 @@ abstract class AbstractNativeLanguageIncrementalBuildIntegrationTest extends Abs
         nonSkippedTasks.empty
     }
 
-    @IgnoreIf({GradleContextualExecuter.parallel || !TestPrecondition.CAN_INSTALL_EXECUTABLE.fulfilled})
+    @IgnoreIf({!TestPrecondition.CAN_INSTALL_EXECUTABLE.fulfilled})
     def "rebuilds executable with source file change"() {
         given:
         run "installMainExecutable"
@@ -246,8 +246,8 @@ abstract class AbstractNativeLanguageIncrementalBuildIntegrationTest extends Abs
     }
 
     // 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. 
+    // 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() &&
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalCompileIntegrationTest.groovy
index 5a17a3b..9fc3feb 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalCompileIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIncrementalCompileIntegrationTest.groovy
@@ -29,6 +29,7 @@ abstract class AbstractNativeLanguageIncrementalCompileIntegrationTest extends A
     String compileTask
     TestFile sourceFile
     TestFile sharedHeaderFile
+    TestFile commonHeaderFile
     TestFile otherHeaderFile
     List<TestFile> otherSourceFiles = []
     TestFile objectFileDir
@@ -57,6 +58,7 @@ abstract class AbstractNativeLanguageIncrementalCompileIntegrationTest extends A
         and:
         sourceFile = app.mainSource.writeToDir(file("src/main"))
         sharedHeaderFile = app.libraryHeader.writeToDir(file("src/main"))
+        commonHeaderFile = app.commonHeader.writeToDir(file("src/main"))
         app.librarySources.each {
             otherSourceFiles << it.writeToDir(file("src/main"))
         }
@@ -271,6 +273,7 @@ abstract class AbstractNativeLanguageIncrementalCompileIntegrationTest extends A
         outputs.snapshot { run "mainExecutable" }
 
         file("src/replacement-headers/${sharedHeaderFile.name}") << sharedHeaderFile.text
+        file("src/replacement-headers/${commonHeaderFile.name}") << commonHeaderFile.text
 
         when:
         buildFile << """
@@ -319,6 +322,7 @@ model {
 
         when:
         file("src/replacement-headers/${sharedHeaderFile.name}") << sharedHeaderFile.text
+        file("src/replacement-headers/${commonHeaderFile.name}") << commonHeaderFile.text
 
         and:
         run "mainExecutable"
@@ -336,6 +340,7 @@ model {
 
         when:
         sourceFile.parentFile.file(sharedHeaderFile.name) << sharedHeaderFile.text
+        sourceFile.parentFile.file(commonHeaderFile.name) << commonHeaderFile.text
 
         and:
         run "mainExecutable"
@@ -344,7 +349,7 @@ model {
         executedAndNotSkipped compileTask
 
         and:
-        outputs.recompiledFiles allSources
+        outputs.recompiledFiles allSources + [commonHeaderFile]
     }
 
     def "recompiles all source files and removes stale outputs when compiler arg changes"() {
@@ -492,6 +497,7 @@ model {
 }
 """
         app.writeSources(file("src/other"))
+        app.commonHeader.writeToDir(file("src/other"))
 
         and:
         outputs.snapshot { run "mainExecutable" }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIntegrationTest.groovy
index 5bb9354..411e4be 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativeLanguageIntegrationTest.groovy
@@ -20,11 +20,13 @@ package org.gradle.language
 import org.apache.commons.lang.RandomStringUtils
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Ignore
 
+ at LeaksFileHandles
 abstract class AbstractNativeLanguageIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     abstract HelloWorldApp getHelloWorldApp()
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativePreCompiledHeaderIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativePreCompiledHeaderIntegrationTest.groovy
index 1cbbea8..76e2436 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativePreCompiledHeaderIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/AbstractNativePreCompiledHeaderIntegrationTest.groovy
@@ -19,42 +19,60 @@ package org.gradle.language
 import org.apache.commons.lang.StringUtils
 import org.gradle.integtests.fixtures.SourceFile
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
-import org.gradle.nativeplatform.fixtures.app.PCHHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
+import org.hamcrest.Matchers
 import org.spockframework.util.TextUtil
 
 abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
-    abstract PCHHelloWorldApp getApp()
+    abstract IncrementalHelloWorldApp getApp()
 
     def "setup"() {
+        settingsFile << "rootProject.name = 'test'"
         buildFile << app.pluginScript
         buildFile << app.extraConfiguration
     }
 
-    def "can set a precompiled header on a source set for a relative source header" () {
+    def "can set a precompiled header on a source set for a source header in the headers directory" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.getLibraryHeader(path).writeToDir(file("src/hello"))
-        app.getLibrarySources(path).each {
-            it.writeToDir(file("src/hello"))
-        }
-        assert file("src/hello/headers/${path}hello.h").exists()
+        writeStandardSourceFiles(path)
+
+        when:
+        buildFile << preCompiledHeaderComponent(path)
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ])
 
         when:
+        librarySourceModified("hello", path)
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled()
+
+        where:
+        path << [ "", "subdir/to/header/" ]
+    }
+
+    def "can set a precompiled header on a source set for a header colocated with the source" () {
+        given:
+        new SourceFile(app.sourceType, "hello.h", app.libraryHeader.content).writeToDir(file("src/hello"))
+        app.librarySources.each { it.writeToDir(file("src/hello")) }
+        new SourceFile(app.sourceType, "common.h", app.commonHeader.content).writeToDir(file("src/hello"))
+
+        when:
+        buildFile << preCompiledHeaderComponent()
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
+                    hello {
                         sources {
-                            ${app.sourceType}.preCompiledHeader "${path}hello.h"
-                        }
-                        binaries.all {
-                            if (toolChain.name == "visualCpp") {
-                                ${app.compilerArgs("/showIncludes")}
-                            } else {
-                                ${app.compilerArgs("-H")}
-                            }
+                            ${app.sourceType}.source.include "**/*.${app.sourceExtension}"
                         }
                     }
                 }
@@ -63,47 +81,38 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling hello.h ====>")
-        def outputDirectories = file(PCHHeaderDirName).listFiles().findAll { it.isDirectory() }
-        assert outputDirectories.size() == 1
-        assert outputDirectories[0].assertContainsDescendants("prefix-headers.${getSuffix()}")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ])
 
-        and:
-        args("--info")
-        succeeds libraryCompileTaskName
-        skipped ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
-        ! output.contains("<==== compiling hello.h ====>")
+        when:
+        librarySourceModified()
 
-        where:
-        path << [ "", "subdir/to/header/" ]
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled()
     }
 
     def "can set a precompiled header on a source set for a source header in include path" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.getLibraryHeader(path).writeToDir(file("src/include"))
-        app.getLibrarySources(path).each {
-            it.writeToDir(file("src/hello"))
-        }
-        assert file("src/include/headers/${path}hello.h").exists()
+        app.libraryHeader.writeToDir(file("src/include"))
+        getLibrarySources(path).each { it.writeToDir(file("src/hello")) }
+        getCommonHeader(path).writeToDir(file("src/include"))
 
         when:
         def headerDir = file("src/include/headers")
         def safeHeaderDirPath = TextUtil.escape(headerDir.absolutePath)
+        buildFile << preCompiledHeaderComponent(path, pch)
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
-                        sources {
-                            ${app.sourceType}.preCompiledHeader "${path}hello.h"
-                        }
+                    hello {
                         binaries.all {
                             if (toolChain.name == "visualCpp") {
-                                ${app.sourceType}Compiler.args "/I${safeHeaderDirPath}", "/showIncludes"
+                                ${app.sourceType}Compiler.args "/I${safeHeaderDirPath}"
                             } else {
-                                ${app.sourceType}Compiler.args "-I${safeHeaderDirPath}", "-H"
+                                ${app.sourceType}Compiler.args "-I${safeHeaderDirPath}"
                             }
                         }
                     }
@@ -113,50 +122,64 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling hello.h ====>")
-        def outputDirectories = file(PCHHeaderDirName).listFiles().findAll { it.isDirectory() }
-        assert outputDirectories.size() == 1
-        assert outputDirectories[0].assertContainsDescendants("prefix-headers.${getSuffix()}")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ])
 
-        and:
+        when:
+        librarySourceModified("hello", path)
+
+        then:
         args("--info")
-        succeeds libraryCompileTaskName
-        skipped ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
-        ! output.contains("<==== compiling hello.h ====>")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled()
 
         where:
-        path << [ "", "subdir/" ]
+        path      | pch
+        ""        | "common.h"
+        "subdir/" | "common.h"
+        ""        | "<common.h>"
     }
 
-    def "can set a precompiled header on a source set for a system header" () {
+    def "a precompiled header on a source set gets used for all variants of a binary" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.libraryHeader.writeToDir(file("src/hello"))
-        app.getSystemHeader(path).writeToDir(file("src/systemHeader"))
-        app.librarySources.each {
-            SourceFile library = new SourceFile(it.path, it.name, "#include <${path}systemHeader.h>\n" + it.content)
-            library.writeToDir(file("src/hello"))
-        }
-        assert file("src/systemHeader/headers/${path}systemHeader.h").exists()
+        writeStandardSourceFiles()
+
+        when:
+        buildFile << preCompiledHeaderComponent()
+
+        then:
+        args("--info")
+        succeeds "assemble"
+        libAndPCHTasksExecuted("hello", "shared")
+        libAndPCHTasksExecuted("hello", "static")
+        pchCompiledOnceForEach([ getPCHHeaderDirName("hello", "shared"), getPCHHeaderDirName("hello", "static") ])
+
+        when:
+        librarySourceModified()
+
+        then:
+        args("--info")
+        succeeds "assemble"
+        pchNotCompiled("hello", "shared")
+        pchNotCompiled("hello", "static")
+    }
+
+    def "can set a precompiled header on multiple source sets" () {
+        given:
+        app.headerFiles.each { it.writeToDir(file("src/hello")) }
+        app.librarySources.find { it.name == "hello.${app.sourceExtension}" }.writeToDir(file("src/hello"))
+        writeOtherSourceSetFiles()
 
         when:
-        def systemHeaderDir = file("src/systemHeader/headers")
-        def safeHeaderDirPath = TextUtil.escape(systemHeaderDir.absolutePath)
+        buildFile << preCompiledHeaderComponent()
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
+                    hello {
                         sources {
-                            ${app.sourceType}.preCompiledHeader "<${path}systemHeader.h>"
-                        }
-                        binaries.all {
-                            println toolChain
-                            if (toolChain.name == "visualCpp") {
-                                ${app.sourceType}Compiler.args "/I${safeHeaderDirPath}", "/showIncludes"
-                            } else {
-                                ${app.sourceType}Compiler.args "-I${safeHeaderDirPath}", "-H"
+                            other(${app.sourceSetType}) {
+                                preCompiledHeader "common2.h"
                             }
                         }
                     }
@@ -166,48 +189,34 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling systemHeader.h ====>")
-        def outputDirectories = file(PCHHeaderDirName).listFiles().findAll { it.isDirectory() }
-        assert outputDirectories.size() == 1
-        assert outputDirectories[0].assertContainsDescendants("prefix-headers.${getSuffix()}")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted("hello", "shared", "${app.sourceType}")
+        libAndPCHTasksExecuted("hello", "shared", "other")
+        pchCompiledOnceForEach([ PCHHeaderDirName, getPCHHeaderDirName("hello", "shared", "other") ])
 
-        and:
-        args("--info")
-        succeeds libraryCompileTaskName
-        skipped ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
-        ! output.contains("<==== compiling systemHeader.h ====>")
+        when:
+        otherLibrarySourceModified()
+        librarySourceModified()
 
-        where:
-        path << [ "", "subdir/" ]
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled("hello", "shared", "${app.sourceType}")
+        pchNotCompiled("hello", "shared", "other")
     }
 
-    def "can set multiple precompiled headers on a source set" () {
+    def "can set a precompiled header on multiple components" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.getLibraryHeader().writeToDir(file("src/hello"))
-        app.getLibrarySources().each {
-            it.writeToDir(file("src/hello"))
-        }
-        assert file("src/hello/headers/hello.h").exists()
+        writeStandardSourceFiles()
+        app.library.writeSources(file("src/hello2"))
 
         when:
+        buildFile << preCompiledHeaderComponent()
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
-                        sources {
-                            ${app.sourceType}.preCompiledHeader "hello.h"
-                            ${app.sourceType}.preCompiledHeader "<${app.IOHeader}>"
-                        }
-                        binaries.all {
-                            if (toolChain.name == "visualCpp") {
-                                ${app.compilerArgs("/showIncludes")}
-                            } else {
-                                ${app.compilerArgs("-H")}
-                            }
-                        }
+                    hello2(NativeLibrarySpec) {
+                        sources.${app.sourceType}.preCompiledHeader "common.h"
                     }
                 }
             }
@@ -215,39 +224,34 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds libraryCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
-        // once for PCH compile, once for compile of sum.c, but not for hello.c
-        output.count(getUniquePragmaOutput("<==== compiling hello.h ====>")) == 2
+        succeeds "helloSharedLibrary", "hello2SharedLibrary"
+        libAndPCHTasksExecuted("hello")
+        libAndPCHTasksExecuted("hello2")
+        pchCompiledOnceForEach([ getPCHHeaderDirName("hello", "shared"), getPCHHeaderDirName("hello2", "shared") ])
+
+        when:
+        librarySourceModified("hello")
+        librarySourceModified("hello2")
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary", "hello2SharedLibrary"
+        pchNotCompiled("hello")
+        pchNotCompiled("hello2")
     }
 
-    def "can have source sets both with and without precompiled headers" () {
+    def "can have components both with and without precompiled headers" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.getLibraryHeader().writeToDir(file("src/hello"))
-        app.getLibrarySources().find { it.name.startsWith("hello") }.writeToDir(file("src/hello"))
-        assert file("src/hello/headers/hello.h").exists()
-
-        app.getLibraryHeader().writeToDir(file("src/hello2"))
-        app.getLibrarySources().find { it.name.startsWith("hello") }.writeToDir(file("src/hello2"))
-        assert file("src/hello2/headers/hello.h").exists()
+        writeStandardSourceFiles()
+        app.libraryHeader.writeToDir(file("src/hello2"))
+        app.librarySources.find { it.name.startsWith("hello") }.writeToDir(file("src/hello2"))
+        app.commonHeader.writeToDir(file("src/hello2"))
 
         when:
+        buildFile << preCompiledHeaderComponent()
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
-                        sources {
-                            ${app.sourceType}.preCompiledHeader "hello.h"
-                        }
-                        binaries.all {
-                            if (toolChain.name == "visualCpp") {
-                                ${app.compilerArgs("/showIncludes")}
-                            } else {
-                                ${app.compilerArgs("-H")}
-                            }
-                        }
-                    }
                     hello2(NativeLibrarySpec)
                 }
             }
@@ -255,40 +259,55 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling hello.h ====>")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ])
+
+        when:
+        librarySourceModified()
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled()
 
         and:
         args("--info")
-        succeeds libraryCompileTaskName, libraryCompileTaskName.replaceAll("Hello", "Hello2")
-        executed ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
+        succeeds "hello2SharedLibrary"
+        executedAndNotSkipped ":${getLibraryCompileTaskName("hello2", "shared")}"
+        notExecuted ":${getGeneratePrefixHeaderTaskName("hello")}", ":${getPCHCompileTaskName("hello", "shared")}"
         // once for hello2.c only
-        output.count(getUniquePragmaOutput("<==== compiling hello.h ====>")) == 1
+        output.count(getUniquePragmaOutput(DEFAULT_PCH_MESSAGE)) == 1
+    }
+
+    def "can have sources that do not use precompiled header" () {
+        given:
+        writeStandardSourceFiles()
+        libraryWithoutPCH.writeToDir(file("src/hello"))
+
+        when:
+        buildFile << preCompiledHeaderComponent()
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        // once for PCH, once for source file without PCH
+        output.count(getUniquePragmaOutput(DEFAULT_PCH_MESSAGE)) == 2
     }
 
     def "compiler arguments set on the binary get used for the precompiled header" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
-        app.getLibraryHeader().writeToDir(file("src/hello"))
-        app.getLibrarySources().find { it.name.startsWith("hello") }.writeToDir(file("src/hello"))
-        assert file("src/hello/headers/hello.h").exists()
+        writeStandardSourceFiles()
 
         when:
+        buildFile << preCompiledHeaderComponent()
         buildFile << """
             model {
                 components {
-                    hello(NativeLibrarySpec) {
-                        sources {
-                            ${app.sourceType}.preCompiledHeader "hello.h"
-                        }
+                    hello {
                         binaries.all {
                             ${app.compilerDefine("FRENCH")}
-                            if (toolChain.name == "visualCpp") {
-                                ${app.compilerArgs("/showIncludes")}
-                            } else {
-                                ${app.compilerArgs("-H")}
-                            }
                         }
                     }
                 }
@@ -297,34 +316,111 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
 
         then:
         args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling bonjour.h ====>")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ], FRENCH_PCH_MESSAGE)
 
-        and:
+        when:
+        librarySourceModified()
+
+        then:
         args("--info")
-        succeeds libraryCompileTaskName
-        skipped ":${generatePrefixHeaderTaskName}", ":${PCHCompileTaskName}"
-        ! output.contains("<==== compiling bonjour.h ====>")
-        ! output.contains("<==== compiling hello.h ====>")
+        succeeds "helloSharedLibrary"
+        pchNotCompiled()
+        ! output.contains(FRENCH_PCH_MESSAGE)
     }
-    
-    @Requires(TestPrecondition.NOT_WINDOWS)
+
     def "precompiled header compile detects changes in header files" () {
         given:
-        settingsFile << "rootProject.name = 'test'"
+        writeStandardSourceFiles()
+
+        when:
+        buildFile << preCompiledHeaderComponent()
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        pchCompiledOnceForEach([ PCHHeaderDirName ])
+
+        when:
+        libraryHeaderModified()
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        executedAndNotSkipped ":${getPCHCompileTaskName("hello", "shared")}", ":${getLibraryCompileTaskName("hello", "shared")}"
+        skipped ":${getGeneratePrefixHeaderTaskName("hello")}"
+        pchCompiledOnceForEach([ PCHHeaderDirName ], ALTERNATE_PCH_MESSAGE)
+    }
+
+    def "produces warning when pch cannot be used" () {
+        given:
+        app.getLibraryHeader().writeToDir(file("src/hello"))
+        def helloDotC = app.getLibrarySources().find { it.name.startsWith("hello") }.writeToDir(file("src/hello"))
+        helloDotC.text = "#include \"hello.h\"\n" + helloDotC.text
+        app.commonHeader.writeToDir(file("src/hello"))
+
+        when:
+        buildFile << preCompiledHeaderComponent()
+
+        then:
+        args("--info")
+        succeeds "helloSharedLibrary"
+        libAndPCHTasksExecuted()
+        // Once for PCH compile, once for hello.c
+        output.count(getUniquePragmaOutput(DEFAULT_PCH_MESSAGE)) == 2
+        output.contains("The source file hello.${app.sourceExtension} includes the header common.h but it is not the first declared header, so the pre-compiled header will not be used.")
+    }
+
+    def "produces compiler error when specified header is missing" () {
+        given:
         app.getLibraryHeader().writeToDir(file("src/hello"))
         app.getLibrarySources().find { it.name.startsWith("hello") }.writeToDir(file("src/hello"))
-        assert file("src/hello/headers/hello.h").exists()
+        assert ! file("src/hello/headers/prefixHeader.h").exists()
+
+        when:
+        buildFile << preCompiledHeaderComponent()
+
+        then:
+        fails "helloSharedLibrary"
+        failure.assertHasDescription("Execution failed for task ':${getPCHCompileTaskName("hello", "shared")}'.")
+        failure.assertThatCause(Matchers.containsString("compiler failed while compiling prefix-headers"))
+    }
+
+    @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    def "can build and run an executable with library using pch" () {
+        given:
+        writeStandardSourceFiles()
+        app.mainSource.writeToDir(file("src/main"))
 
         when:
         buildFile << """
+            $mainComponent
+            ${preCompiledHeaderComponent()}
+        """
+
+        then:
+        succeeds "installMainExecutable"
+        libAndPCHTasksExecuted()
+
+        and:
+        def install = installation("build/install/mainExecutable")
+        install.assertInstalled()
+        install.exec().out == app.englishOutput
+    }
+
+    static final String DEFAULT_PCH_MESSAGE="<==== compiling hello.h ====>"
+    static final String FRENCH_PCH_MESSAGE="<==== compiling bonjour.h ====>"
+    static final String ALTERNATE_PCH_MESSAGE="<==== compiling alternate hello.h ====>"
+
+    def preCompiledHeaderComponent(String path="", String pch="common.h") {
+        """
             model {
                 components {
                     hello(NativeLibrarySpec) {
                         sources {
-                            ${app.sourceType}.preCompiledHeader "hello.h"
-                            ${app.sourceType}.preCompiledHeader "<${app.IOHeader}>"
+                            ${app.sourceType}.preCompiledHeader "${path}${pch}"
                         }
                         binaries.all {
                             if (toolChain.name == "visualCpp") {
@@ -337,21 +433,99 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
                 }
             }
         """
+    }
 
-        then:
-        args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling hello.h ====>")
+    String getMainComponent() {
+        return """
+            model {
+                components {
+                    main(NativeExecutableSpec) {
+                        sources {
+                            ${app.sourceType}.lib library: "hello"
+                        }
+                    }
+                }
+            }
+        """
+    }
 
-        when:
-        app.alternate.libraryHeader.writeToDir(file("src/hello"))
+    def writeStandardSourceFiles(path="") {
+        app.libraryHeader.writeToDir(file("src/hello"))
+        getLibrarySources(path).each { it.writeToDir(file("src/hello")) }
+        getCommonHeader(path).writeToDir(file("src/hello"))
+        assert file("src/hello/headers/${path}common.h").exists()
+    }
 
-        then:
-        args("--info")
-        succeeds PCHCompileTaskName
-        executed ":${generatePrefixHeaderTaskName}"
-        output.contains("<==== compiling althello.h ====>")
+    def writeOtherSourceSetFiles() {
+        def sumSourceFile = app.librarySources.find { it.name == "sum.${app.sourceExtension}" }
+        replaceInSourceFile(toOtherSourceSet(sumSourceFile), 'include "common.h"', 'include "common2.h"').writeToDir(file("src/hello"))
+        renameSourceFile(app.commonHeader, "common2.h").writeToDir(file("src/hello"))
+    }
+
+    def toOtherSourceSet(SourceFile sourceFile) {
+        return new SourceFile("other", sourceFile.name, sourceFile.content)
+    }
+
+    def renameSourceFile(SourceFile sourceFile, String name) {
+        return new SourceFile(sourceFile.path, name, sourceFile.content)
+    }
+
+    def addFunction(SourceFile sourceFile) {
+        return new SourceFile(sourceFile.path, sourceFile.name, sourceFile.content + """
+            // Extra function to ensure library has different size
+            int otherFunction() {
+                return 1000;
+            }
+        """)
+    }
+
+    def otherLibrarySourceModified() {
+        def sumSourceFile = app.alternateLibrarySources.find { it.name == "sum.${app.sourceExtension}" }
+        def alternateSumFile = addFunction(sumSourceFile)
+        replaceInSourceFile(toOtherSourceSet(alternateSumFile), 'include "common.h"', 'include "common2.h"').writeToDir(file("src/hello"))
+    }
+
+    def librarySourceModified(String lib="hello", String path="") {
+        getAlternateLibrarySources(path).find { it.name == "hello.${app.sourceExtension}" }.writeToDir(file("src/${lib}"))
+        maybeWait()
+    }
+
+    def libraryHeaderModified() {
+        alternateLibraryHeader.writeToDir(file("src/hello"))
+        maybeWait()
+    }
+
+    def libAndPCHTasksExecuted(String lib="hello", String linkage="shared", String sourceSet=app.sourceType) {
+        executedAndNotSkipped(":${getPCHCompileTaskName(lib, linkage, sourceSet)}", ":${getLibraryCompileTaskName(lib, linkage, sourceSet)}", ":${getGeneratePrefixHeaderTaskName(lib, sourceSet)}")
+        true
+    }
+
+    def pchCompiledOnceForEach(List pchDirs, message=DEFAULT_PCH_MESSAGE) {
+        assert output.count(getUniquePragmaOutput(message)) == pchDirs.size()
+        pchDirs.each { pchHeaderDirName ->
+            def outputDirectories = file(pchHeaderDirName).listFiles().findAll { it.isDirectory() }
+            assert outputDirectories.size() == 1
+            assert outputDirectories[0].assertContainsDescendants("prefix-headers.${getSuffix()}")
+        }
+        true
+    }
+
+    def pchNotCompiled(String lib="hello", String linkage="shared", String sourceSet=app.sourceType) {
+        def pchCompileTask = getPCHCompileTaskName(lib, linkage)
+        def compileTask = getLibraryCompileTaskName(lib, linkage, sourceSet)
+        def generateTask = getGeneratePrefixHeaderTaskName(lib, sourceSet)
+        executedAndNotSkipped ":${compileTask}"
+        skipped ":${pchCompileTask}", ":${generateTask}"
+        assert output.count(getUniquePragmaOutput(DEFAULT_PCH_MESSAGE)) == 0
+        true
+    }
+
+    private void maybeWait() {
+        if (toolChain.visualCpp) {
+            def now = System.currentTimeMillis()
+            def nextSecond = now % 1000
+            Thread.sleep(1200 - nextSecond)
+        }
     }
 
     String getSuffix() {
@@ -368,19 +542,55 @@ abstract class AbstractNativePreCompiledHeaderIntegrationTest extends AbstractIn
         }
     }
 
-    String getPCHCompileTaskName() {
-        return "compileHelloSharedLibrary${StringUtils.capitalize(app.sourceType)}PreCompiledHeader"
+    List<SourceFile> getLibrarySources(String headerPath) {
+        updateCommonHeaderPath(app.getLibrarySources(), headerPath)
+    }
+
+    List<SourceFile> getAlternateLibrarySources(String headerPath) {
+        updateCommonHeaderPath(app.getAlternateLibrarySources(), headerPath)
+    }
+
+    SourceFile getCommonHeader(String path) {
+        updateSourceFilePath(app.getCommonHeader(), path)
+    }
+
+    SourceFile getAlternateLibraryHeader() {
+        replaceInSourceFile(app.getLibraryHeader(), "compiling hello.h", "compiling alternate hello.h")
+    }
+
+    SourceFile getLibraryWithoutPCH() {
+        def original = app.getLibrarySources().find { it.name == "sum.${app.sourceExtension}" }
+        replaceInSourceFile(original, "include \"common.h\"", "include \"hello.h\"")
+    }
+
+    static List<SourceFile> updateCommonHeaderPath(List<SourceFile> sourceFiles, String headerPath) {
+        return sourceFiles.collect {
+            def newContent = it.content.replaceAll("#include \"common.h\"", "#include \"${headerPath}common.h\"")
+            new SourceFile(it.path, it.name, newContent)
+        }
+    }
+
+    static SourceFile replaceInSourceFile(SourceFile sourceFile, String text, String replacement) {
+        new SourceFile(sourceFile.path, sourceFile.name, sourceFile.content.replaceAll(text, replacement))
+    }
+
+    static SourceFile updateSourceFilePath(SourceFile sourceFile, String path) {
+        new SourceFile("${sourceFile.path}/${path}", sourceFile.name, sourceFile.content)
+    }
+
+    String getPCHCompileTaskName(String lib, String linkage, String sourceSet=app.sourceType) {
+        return "compile${StringUtils.capitalize(lib)}${StringUtils.capitalize(linkage)}Library${StringUtils.capitalize(sourceSet)}PreCompiledHeader"
     }
 
-    String getGeneratePrefixHeaderTaskName() {
-        return "generate${StringUtils.capitalize(app.sourceType)}PrefixHeaderFile"
+    String getGeneratePrefixHeaderTaskName(String lib, String sourceSet=app.sourceType) {
+        return "generate${StringUtils.capitalize(lib)}${StringUtils.capitalize(sourceSet)}PrefixHeaderFile"
     }
 
-    String getLibraryCompileTaskName() {
-        return "compileHelloSharedLibraryHello${StringUtils.capitalize(app.sourceType)}"
+    String getLibraryCompileTaskName(String lib, String linkage, String sourceSet=app.sourceType) {
+        return "compile${StringUtils.capitalize(lib)}${StringUtils.capitalize(linkage)}Library${StringUtils.capitalize(lib)}${StringUtils.capitalize(sourceSet)}"
     }
 
-    String getPCHHeaderDirName() {
-        return "build/objs/helloSharedLibrary/hello${StringUtils.capitalize(app.sourceType)}PreCompiledHeader"
+    String getPCHHeaderDirName(String lib="hello", String linkage="shared", String sourceSet=app.sourceType) {
+        return "build/objs/${lib}${StringUtils.capitalize(linkage)}Library/${lib}${StringUtils.capitalize(sourceSet)}PCH"
     }
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/DuplicateBaseNamesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/DuplicateBaseNamesIntegrationTest.groovy
index 6ec59ce..4bdde21 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/DuplicateBaseNamesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/DuplicateBaseNamesIntegrationTest.groovy
@@ -20,12 +20,14 @@ import org.gradle.integtests.fixtures.SourceFile
 import org.gradle.language.fixtures.app.*
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VisualCpp
 
 // TODO add coverage for mixed sources
+ at LeaksFileHandles
 class DuplicateBaseNamesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def "can have sourcefiles with same base name but different directories"() {
@@ -172,4 +174,4 @@ model {
         succeeds "mainExecutable"
         executable("build/binaries/mainExecutable/main").exec().out == "foo1foo2"
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIncrementalBuildIntegrationTest.groovy
index d20d628..adde374 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIncrementalBuildIntegrationTest.groovy
@@ -20,11 +20,13 @@ import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.IgnoreIf
 
+ at LeaksFileHandles
 class AssemblyLanguageIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     HelloWorldApp app = new MixedLanguageHelloWorldApp(AbstractInstalledToolChainIntegrationSpec.toolChain)
@@ -73,6 +75,7 @@ class AssemblyLanguageIncrementalBuildIntegrationTest extends AbstractInstalledT
     }
 
     @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+    @LeaksFileHandles
     def "reassembles binary with assembler option change"() {
         when:
         buildFile << """
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIntegrationTest.groovy
index d0c9b8f..179bb0d 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/assembler/AssemblyLanguageIntegrationTest.groovy
@@ -23,9 +23,11 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.AvailableToolChains
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.gradle.util.Matchers.containsText
 
+ at LeaksFileHandles
 class AssemblyLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     HelloWorldApp helloWorldApp = new AssemblerWithCHelloWorldApp(AbstractInstalledToolChainIntegrationSpec.toolChain)
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIncrementalBuildIntegrationTest.groovy
index 1fc44df..8a0f645 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIncrementalBuildIntegrationTest.groovy
@@ -18,7 +18,9 @@ package org.gradle.language.c
 import org.gradle.language.AbstractNativeLanguageIncrementalBuildIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CLanguageIncrementalBuildIntegrationTest extends AbstractNativeLanguageIncrementalBuildIntegrationTest {
     @Override
     IncrementalHelloWorldApp getHelloWorldApp() {
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIntegrationTest.groovy
index b00d082..87f0206 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CLanguageIntegrationTest.groovy
@@ -18,11 +18,13 @@ import org.gradle.language.AbstractNativeLanguageIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.CCompilerDetectingTestApp
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Issue
 import spock.lang.Unroll
 
 import static org.gradle.util.Matchers.containsText
 // TODO:DAZ Some of these tests should apply to all single-language integration tests
+ at LeaksFileHandles
 class CLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     HelloWorldApp helloWorldApp = new CHelloWorldApp()
@@ -49,7 +51,7 @@ class CLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     def "can manually define C source sets"() {
         given:
-        helloWorldApp.getLibraryHeader().writeToDir(file("src/shared"))
+        helloWorldApp.library.headerFiles.each { it.writeToDir(file("src/shared")) }
 
         file("src/main/c/main.c") << helloWorldApp.mainSource.content
         file("src/main/c2/hello.c") << helloWorldApp.librarySources[0].content
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CPreCompiledHeaderSourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CPreCompiledHeaderSourcesIntegrationTest.groovy
index 3b6da67..a6c213b 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CPreCompiledHeaderSourcesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CPreCompiledHeaderSourcesIntegrationTest.groovy
@@ -16,13 +16,15 @@
 
 package org.gradle.language.c
 
-import org.gradle.nativeplatform.fixtures.app.CPCHHelloWorldApp
-import org.gradle.nativeplatform.fixtures.app.PCHHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
 import org.gradle.language.AbstractNativePreCompiledHeaderIntegrationTest
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CPreCompiledHeaderSourcesIntegrationTest extends AbstractNativePreCompiledHeaderIntegrationTest {
     @Override
-    PCHHelloWorldApp getApp() {
-        return new CPCHHelloWorldApp()
+    IncrementalHelloWorldApp getApp() {
+        return new CHelloWorldApp()
     }
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CppCallingCLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CppCallingCLanguageIntegrationTest.groovy
index f454264..d508c5a 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CppCallingCLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/CppCallingCLanguageIntegrationTest.groovy
@@ -19,7 +19,9 @@ package org.gradle.language.c
 import org.gradle.language.AbstractNativeLanguageIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.CppCallingCHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CppCallingCLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
     HelloWorldApp helloWorldApp = new CppCallingCHelloWorldApp()
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/MixedLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/MixedLanguageIntegrationTest.groovy
index 7670e6f..ca669d1 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/MixedLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/c/MixedLanguageIntegrationTest.groovy
@@ -20,7 +20,9 @@ import org.gradle.integtests.fixtures.SourceFile
 import org.gradle.language.AbstractNativeLanguageIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.MixedLanguageHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class MixedLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     HelloWorldApp helloWorldApp = new MixedLanguageHelloWorldApp(toolChain)
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy
index b763dd9..0d58c54 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalBuildIntegrationTest.groovy
@@ -19,7 +19,9 @@ package org.gradle.language.cpp
 import org.gradle.language.AbstractNativeLanguageIncrementalBuildIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CppLanguageIncrementalBuildIntegrationTest extends AbstractNativeLanguageIncrementalBuildIntegrationTest {
     IncrementalHelloWorldApp getHelloWorldApp() {
         new CppHelloWorldApp()
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy
index ab9c039..6bf6ec4 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIncrementalCompileIntegrationTest.groovy
@@ -18,7 +18,9 @@ package org.gradle.language.cpp
 import org.gradle.language.AbstractNativeLanguageIncrementalCompileIntegrationTest
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CppLanguageIncrementalCompileIntegrationTest extends AbstractNativeLanguageIncrementalCompileIntegrationTest {
      @Override
      IncrementalHelloWorldApp getHelloWorldApp() {
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIntegrationTest.groovy
index fd5b17e..b604f19 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppLanguageIntegrationTest.groovy
@@ -21,9 +21,11 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.app.CppCompilerDetectingTestApp
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.gradle.util.Matchers.containsText
 
+ at LeaksFileHandles
 class CppLanguageIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     HelloWorldApp helloWorldApp = new CppHelloWorldApp()
@@ -74,7 +76,7 @@ model {
 
     def "can manually define C++ source sets"() {
         given:
-        helloWorldApp.getLibraryHeader().writeToDir(file("src/shared"))
+        helloWorldApp.library.headerFiles.each { it.writeToDir(file("src/shared")) }
 
         file("src/main/cpp/main.cpp") << helloWorldApp.mainSource.content
         file("src/main/cpp2/hello.cpp") << helloWorldApp.librarySources[0].content
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy
index 2e99012..fd2fb06 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy
@@ -16,13 +16,15 @@
 
 package org.gradle.language.cpp
 
-import org.gradle.nativeplatform.fixtures.app.CppPCHHelloWorldApp
-import org.gradle.nativeplatform.fixtures.app.PCHHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
 import org.gradle.language.AbstractNativePreCompiledHeaderIntegrationTest
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class CppPreCompiledHeaderSourcesIntegrationTest extends AbstractNativePreCompiledHeaderIntegrationTest {
     @Override
-    PCHHelloWorldApp getApp() {
-        return new CppPCHHelloWorldApp()
+    IncrementalHelloWorldApp getApp() {
+        return new CppHelloWorldApp()
     }
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/nativeplatform/NativeLanguageSamplesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/nativeplatform/NativeLanguageSamplesIntegrationTest.groovy
index e32f665..868add1 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/nativeplatform/NativeLanguageSamplesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/nativeplatform/NativeLanguageSamplesIntegrationTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
@@ -28,6 +29,7 @@ import spock.lang.IgnoreIf
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VisualCpp
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class NativeLanguageSamplesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     @Rule final TestNameTestDirectoryProvider testDirProvider = new TestNameTestDirectoryProvider()
     @Rule public final Sample assembler = sample(testDirProvider, 'assembler')
@@ -39,6 +41,7 @@ class NativeLanguageSamplesIntegrationTest extends AbstractInstalledToolChainInt
     @Rule public final Sample windowsResources = sample(testDirProvider, 'windows-resources')
     @Rule public final Sample idl = sample(testDirProvider, 'idl')
     @Rule public final Sample cunit = sample(testDirProvider, 'cunit')
+    @Rule public final Sample pch = sample(testDirProvider, 'pre-compiled-headers')
 
     private static Sample sample(TestDirectoryProvider testDirectoryProvider, String name) {
         return new Sample(testDirectoryProvider, "native-binaries/${name}", name)
@@ -63,10 +66,10 @@ class NativeLanguageSamplesIntegrationTest extends AbstractInstalledToolChainInt
     def "c"() {
         given:
         sample c
-        
+
         when:
         run "installMainExecutable"
-        
+
         then:
         executedAndNotSkipped ":compileHelloSharedLibraryHelloC", ":linkHelloSharedLibrary", ":helloSharedLibrary",
                               ":compileMainExecutableMainC", ":linkMainExecutable", ":mainExecutable"
@@ -173,4 +176,20 @@ class NativeLanguageSamplesIntegrationTest extends AbstractInstalledToolChainInt
         and:
         installation(idl.dir.file("build/install/mainExecutable")).exec().out == "Hello from generated source!!\n"
     }
-}
\ No newline at end of file
+
+    def "pch"() {
+        given:
+        sample pch
+
+        when:
+        run "installMainExecutable"
+
+        then:
+        executedAndNotSkipped ":generateHelloCppPrefixHeaderFile", ":compileHelloSharedLibraryCppPreCompiledHeader",
+                              ":linkHelloSharedLibrary", ":helloSharedLibrary",
+                              ":compileMainExecutableMainCpp", ":linkMainExecutable", ":mainExecutable"
+
+        and:
+        installation(pch.dir.file("build/install/mainExecutable")).exec().out == "Hello world!\n"
+    }
+}
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy
index 26c156b..3b1c8ef 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCLanguageIncrementalBuildIntegrationTest.groovy
@@ -24,7 +24,7 @@ import org.gradle.util.TestPrecondition
 import spock.lang.Ignore
 
 @Requires(TestPrecondition.OBJECTIVE_C_SUPPORT)
-class ObjectiveCLanguageIncrementalBuildIntegrationTest extends AbstractNativeLanguageIncrementalBuildIntegrationTest{
+class ObjectiveCLanguageIncrementalBuildIntegrationTest extends AbstractNativeLanguageIncrementalBuildIntegrationTest {
 
     @Override
     boolean isCanBuildForMultiplePlatforms() {
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCPreCompiledHeaderSourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCPreCompiledHeaderSourcesIntegrationTest.groovy
index 97752c0..bdf33e5 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCPreCompiledHeaderSourcesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCPreCompiledHeaderSourcesIntegrationTest.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.language.objectivec
 
-import org.gradle.nativeplatform.fixtures.app.ObjectiveCPCHHelloWorldApp
-import org.gradle.nativeplatform.fixtures.app.PCHHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.ObjectiveCHelloWorldApp
 import org.gradle.language.AbstractNativePreCompiledHeaderIntegrationTest
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -25,7 +25,7 @@ import org.gradle.util.TestPrecondition
 @Requires(TestPrecondition.OBJECTIVE_C_SUPPORT)
 class ObjectiveCPreCompiledHeaderSourcesIntegrationTest extends AbstractNativePreCompiledHeaderIntegrationTest {
     @Override
-    PCHHelloWorldApp getApp() {
-        return new ObjectiveCPCHHelloWorldApp()
+    IncrementalHelloWorldApp getApp() {
+        return new ObjectiveCHelloWorldApp()
     }
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy
index 667551f..3039b23 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivec/ObjectiveCUnsupportedIntegrationTest.groovy
@@ -20,10 +20,12 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.ToolChainRequirement
 import org.gradle.nativeplatform.fixtures.app.ObjectiveCHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.hamcrest.CoreMatchers.containsString
 
 @RequiresInstalledToolChain(ToolChainRequirement.VisualCpp)
+ at LeaksFileHandles
 class ObjectiveCUnsupportedIntegrationTest extends AbstractInstalledToolChainIntegrationSpec{
 
     def helloWorldApp = new ObjectiveCHelloWorldApp();
@@ -52,4 +54,4 @@ model {
         then:
         failure.assertThatCause(containsString("Objective-C is not available on the Visual C++ toolchain"))
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppPreCompiledHeaderSourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppPreCompiledHeaderSourcesIntegrationTest.groovy
index 6ea1866..4bfc883 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppPreCompiledHeaderSourcesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppPreCompiledHeaderSourcesIntegrationTest.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.language.objectivecpp
 
-import org.gradle.nativeplatform.fixtures.app.ObjectiveCppPCHHelloWorldApp
-import org.gradle.nativeplatform.fixtures.app.PCHHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.IncrementalHelloWorldApp
+import org.gradle.nativeplatform.fixtures.app.ObjectiveCppHelloWorldApp
 import org.gradle.language.AbstractNativePreCompiledHeaderIntegrationTest
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -25,7 +25,7 @@ import org.gradle.util.TestPrecondition
 @Requires(TestPrecondition.OBJECTIVE_C_SUPPORT)
 class ObjectiveCppPreCompiledHeaderSourcesIntegrationTest extends AbstractNativePreCompiledHeaderIntegrationTest {
     @Override
-    PCHHelloWorldApp getApp() {
-        return new ObjectiveCppPCHHelloWorldApp()
+    IncrementalHelloWorldApp getApp() {
+        return new ObjectiveCppHelloWorldApp()
     }
 }
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy
index 97a3c5c..6ff3df5 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/objectivecpp/ObjectiveCppUnsupportedIntegrationTest.groovy
@@ -20,11 +20,13 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.ToolChainRequirement
 import org.gradle.nativeplatform.fixtures.app.ObjectiveCppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.hamcrest.CoreMatchers.containsString
 
 
 @RequiresInstalledToolChain(ToolChainRequirement.VisualCpp)
+ at LeaksFileHandles
 class ObjectiveCppUnsupportedIntegrationTest extends AbstractInstalledToolChainIntegrationSpec{
 
     def helloWorldApp = new ObjectiveCppHelloWorldApp();
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIncrementalBuildIntegrationTest.groovy
index 868898f..5429499 100644
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIncrementalBuildIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIncrementalBuildIntegrationTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.nativeplatform.fixtures.ExecutableFixture
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.WindowsResourceHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.IgnoreIf
 
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VisualCpp
@@ -65,6 +66,7 @@ model {
         nonSkippedTasks.empty
     }
 
+    @LeaksFileHandles
     def "compiles and links when resource source changes"() {
         when:
         file("src/main/rc/resources.rc").text = """
diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIntegrationTest.groovy
index 8087ce9..ac87d28 100755
--- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIntegrationTest.groovy
+++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/rc/WindowsResourcesIntegrationTest.groovy
@@ -21,12 +21,14 @@ import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.WindowsResourceHelloWorldApp
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Ignore
 
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VisualCpp
 import static org.gradle.util.Matchers.containsText
 
 @RequiresInstalledToolChain(VisualCpp)
+ at LeaksFileHandles
 class WindowsResourcesIntegrationTest extends AbstractNativeLanguageIntegrationTest {
 
     HelloWorldApp helloWorldApp = new WindowsResourceHelloWorldApp()
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPCHPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPCHPlugin.java
deleted file mode 100644
index 959c885..0000000
--- a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPCHPlugin.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.Maps;
-import org.gradle.language.base.internal.SourceTransformTaskConfig;
-import org.gradle.language.c.CSourceSet;
-import org.gradle.language.c.tasks.CPreCompiledHeaderCompile;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
-import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
-import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
-import org.gradle.model.Mutate;
-import org.gradle.model.RuleSource;
-import org.gradle.nativeplatform.internal.pch.PreCompiledHeaderTransformContainer;
-
-import java.util.Map;
-
-/**
- * Adds support for compiling C pre-compiled headers.
- */
- at SuppressWarnings("UnusedDeclaration")
-public class CLangPCHPlugin extends RuleSource {
-    @Mutate
-    void registerPreCompiledHeaderTask(PreCompiledHeaderTransformContainer pchTransformContainer) {
-        pchTransformContainer.add(new CPCH());
-    }
-
-    private static class CPCH extends NativeLanguageTransform<CSourceSet> {
-        public Class<CSourceSet> getSourceSetType() {
-            return CSourceSet.class;
-        }
-
-        public Map<String, Class<?>> getBinaryTools() {
-            Map<String, Class<?>> tools = Maps.newLinkedHashMap();
-            tools.put("cCompiler", DefaultPreprocessingTool.class);
-            return tools;
-        }
-
-        @Override
-        public SourceTransformTaskConfig getTransformTask() {
-            return new PCHCompileTaskConfig(this, CPreCompiledHeaderCompile.class);
-        }
-    }
-}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPlugin.java
index 323773b..9582a43 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CLangPlugin.java
@@ -26,11 +26,14 @@ import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.language.c.CSourceSet;
 import org.gradle.language.c.internal.DefaultCSourceSet;
 import org.gradle.language.c.tasks.CCompile;
-import org.gradle.language.nativeplatform.internal.CompileTaskConfig;
+import org.gradle.language.c.tasks.CPreCompiledHeaderCompile;
 import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
+import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
+import org.gradle.language.nativeplatform.internal.SourceCompileTaskConfig;
 import org.gradle.model.Mutate;
 import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.internal.pch.PchEnabledLanguageTransform;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
 
@@ -60,7 +63,7 @@ public class CLangPlugin implements Plugin<Project> {
         }
     }
 
-    private static class C extends NativeLanguageTransform<CSourceSet> {
+    private static class C extends NativeLanguageTransform<CSourceSet> implements PchEnabledLanguageTransform<CSourceSet> {
         public Class<CSourceSet> getSourceSetType() {
             return CSourceSet.class;
         }
@@ -72,7 +75,11 @@ public class CLangPlugin implements Plugin<Project> {
         }
 
         public SourceTransformTaskConfig getTransformTask() {
-            return new CompileTaskConfig(this, CCompile.class);
+            return new SourceCompileTaskConfig(this, CCompile.class);
+        }
+
+        public SourceTransformTaskConfig getPchTransformTask() {
+            return new PCHCompileTaskConfig(this, CPreCompiledHeaderCompile.class);
         }
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CPlugin.java
index b6caaa1..0ce1670 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/c/plugins/CPlugin.java
@@ -33,7 +33,6 @@ public class CPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPluginManager().apply(NativeComponentPlugin.class);
         project.getPluginManager().apply(CLangPlugin.class);
-        project.getPluginManager().apply(CLangPCHPlugin.class);
     }
 
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/c/tasks/CCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/c/tasks/CCompile.java
index bed2dbf..e8d9e67 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/c/tasks/CCompile.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/c/tasks/CCompile.java
@@ -18,7 +18,7 @@ package org.gradle.language.c.tasks;
 import org.gradle.api.Incubating;
 import org.gradle.api.tasks.ParallelizableTask;
 import org.gradle.language.c.internal.DefaultCCompileSpec;
-import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
 import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 
 /**
@@ -26,7 +26,7 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
  */
 @Incubating
 @ParallelizableTask
-public class CCompile extends AbstractNativeCompileTask {
+public class CCompile extends AbstractNativeSourceCompileTask {
     @Override
     protected NativeCompileSpec createCompileSpec() {
         return new DefaultCCompileSpec();
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPCHPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPCHPlugin.java
deleted file mode 100644
index 0356558..0000000
--- a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPCHPlugin.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.Maps;
-import org.gradle.language.base.internal.SourceTransformTaskConfig;
-import org.gradle.language.cpp.CppSourceSet;
-import org.gradle.language.cpp.tasks.CppPreCompiledHeaderCompile;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
-import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
-import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
-import org.gradle.model.Mutate;
-import org.gradle.model.RuleSource;
-import org.gradle.nativeplatform.internal.pch.PreCompiledHeaderTransformContainer;
-
-import java.util.Map;
-
-/**
- * Adds support for compiling C++ pre-compiled headers.
- */
- at SuppressWarnings("UnusedDeclaration")
-public class CppLangPCHPlugin extends RuleSource {
-    @Mutate
-    void registerPreCompiledHeaderTask(PreCompiledHeaderTransformContainer pchTransformContainer) {
-        pchTransformContainer.add(new CppPCH());
-    }
-
-
-    private static class CppPCH extends NativeLanguageTransform<CppSourceSet> {
-        public Class<CppSourceSet> getSourceSetType() {
-            return CppSourceSet.class;
-        }
-
-        public Map<String, Class<?>> getBinaryTools() {
-            Map<String, Class<?>> tools = Maps.newLinkedHashMap();
-            tools.put("cppCompiler", DefaultPreprocessingTool.class);
-            return tools;
-        }
-
-        @Override
-        public SourceTransformTaskConfig getTransformTask() {
-            return new PCHCompileTaskConfig(this, CppPreCompiledHeaderCompile.class);
-        }
-    }
-}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPlugin.java
index ea922bb..9bf7299 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppLangPlugin.java
@@ -26,11 +26,14 @@ import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.language.cpp.CppSourceSet;
 import org.gradle.language.cpp.internal.DefaultCppSourceSet;
 import org.gradle.language.cpp.tasks.CppCompile;
-import org.gradle.language.nativeplatform.internal.CompileTaskConfig;
+import org.gradle.language.cpp.tasks.CppPreCompiledHeaderCompile;
 import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
+import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
+import org.gradle.language.nativeplatform.internal.SourceCompileTaskConfig;
 import org.gradle.model.Mutate;
 import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.internal.pch.PchEnabledLanguageTransform;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
 
@@ -59,7 +62,7 @@ public class CppLangPlugin implements Plugin<Project> {
         }
     }
 
-    private static class Cpp extends NativeLanguageTransform<CppSourceSet> {
+    private static class Cpp extends NativeLanguageTransform<CppSourceSet> implements PchEnabledLanguageTransform<CppSourceSet> {
         public Class<CppSourceSet> getSourceSetType() {
             return CppSourceSet.class;
         }
@@ -71,7 +74,11 @@ public class CppLangPlugin implements Plugin<Project> {
         }
 
         public SourceTransformTaskConfig getTransformTask() {
-            return new CompileTaskConfig(this, CppCompile.class);
+            return new SourceCompileTaskConfig(this, CppCompile.class);
+        }
+
+        public SourceTransformTaskConfig getPchTransformTask() {
+            return new PCHCompileTaskConfig(this, CppPreCompiledHeaderCompile.class);
         }
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppPlugin.java
index 75d5fe9..4231200 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/cpp/plugins/CppPlugin.java
@@ -33,6 +33,5 @@ public class CppPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPluginManager().apply(NativeComponentPlugin.class);
         project.getPluginManager().apply(CppLangPlugin.class);
-        project.getPluginManager().apply(CppLangPCHPlugin.class);
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/cpp/tasks/CppCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/cpp/tasks/CppCompile.java
index 4577670..c24b454 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/cpp/tasks/CppCompile.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/cpp/tasks/CppCompile.java
@@ -17,8 +17,8 @@ package org.gradle.language.cpp.tasks;
 
 import org.gradle.api.Incubating;
 import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
 import org.gradle.language.cpp.internal.DefaultCppCompileSpec;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
 import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 
 /**
@@ -26,7 +26,7 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
  */
 @Incubating
 @ParallelizableTask
-public class CppCompile extends AbstractNativeCompileTask {
+public class CppCompile extends AbstractNativeSourceCompileTask {
     @Override
     protected NativeCompileSpec createCompileSpec() {
         return new DefaultCppCompileSpec();
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractHeaderExportingDependentSourceSet.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractHeaderExportingDependentSourceSet.java
index b1d1ecc..09fb209 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractHeaderExportingDependentSourceSet.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractHeaderExportingDependentSourceSet.java
@@ -15,9 +15,7 @@
  */
 package org.gradle.language.nativeplatform.internal;
 
-import com.google.common.collect.Sets;
 import org.gradle.language.base.LanguageSourceSet;
-import org.gradle.language.nativeplatform.DependentSourceSet;
 import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
 import org.gradle.util.CollectionUtils;
 
@@ -25,16 +23,15 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 /**
  * A convenience base class for implementing language source sets with dependencies and exported headers.
  */
 public abstract class AbstractHeaderExportingDependentSourceSet extends AbstractHeaderExportingSourceSet
-        implements HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSet {
+        implements HeaderExportingSourceSet, LanguageSourceSet, DependentSourceSetInternal {
 
     private final List<Object> libs = new ArrayList<Object>();
-    private Set<String> preCompiledHeaders = Sets.newLinkedHashSet();
+    private String preCompiledHeader;
     private File prefixHeaderFile;
 
     public Collection<?> getLibs() {
@@ -51,13 +48,13 @@ public abstract class AbstractHeaderExportingDependentSourceSet extends Abstract
     }
 
     @Override
-    public Set<String> getPreCompiledHeaders() {
-        return preCompiledHeaders;
+    public String getPreCompiledHeader() {
+        return preCompiledHeader;
     }
 
     @Override
-    public void preCompiledHeader(String header) {
-        this.preCompiledHeaders.add(header);
+    public void setPreCompiledHeader(String header) {
+        this.preCompiledHeader = header;
     }
 
     @Override
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractNativeCompileSpec.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractNativeCompileSpec.java
index 483007e..8be1866 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractNativeCompileSpec.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/AbstractNativeCompileSpec.java
@@ -36,7 +36,7 @@ public abstract class AbstractNativeCompileSpec extends AbstractBinaryToolSpec i
     private File prefixHeaderFile;
     private File preCompiledHeaderObjectFile;
     private Map<File, SourceIncludes> sourceFileIncludes;
-    private Set<String> preCompiledHeaders;
+    private String preCompiledHeader;
 
     public List<File> getIncludeRoots() {
         return includeRoots;
@@ -137,12 +137,13 @@ public abstract class AbstractNativeCompileSpec extends AbstractBinaryToolSpec i
     }
 
     @Override
-    public Set<String> getPreCompiledHeaders() {
-        return preCompiledHeaders;
+    public String getPreCompiledHeader() {
+        return preCompiledHeader;
     }
 
-    public void setPreCompiledHeaders(Set<String> preCompiledHeaders) {
-        this.preCompiledHeaders = preCompiledHeaders;
+    @Override
+    public void setPreCompiledHeader(String preCompiledHeader) {
+        this.preCompiledHeader = preCompiledHeader;
     }
 
     private void addAll(List<File> list, Iterable<File> iterable) {
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/CompileTaskConfig.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/CompileTaskConfig.java
index a263f00..3b9c20f 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/CompileTaskConfig.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/CompileTaskConfig.java
@@ -16,14 +16,10 @@
 package org.gradle.language.nativeplatform.internal;
 
 import org.gradle.api.DefaultTask;
-import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.Transformer;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.language.PreprocessingTool;
 import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.language.base.internal.LanguageSourceSetInternal;
@@ -32,7 +28,6 @@ import org.gradle.language.base.internal.registry.LanguageTransform;
 import org.gradle.language.nativeplatform.DependentSourceSet;
 import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
 import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
-import org.gradle.language.nativeplatform.tasks.AbstractNativePCHCompileTask;
 import org.gradle.nativeplatform.NativeDependencySet;
 import org.gradle.nativeplatform.ObjectFile;
 import org.gradle.nativeplatform.SharedLibraryBinarySpec;
@@ -47,7 +42,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
 
-public class CompileTaskConfig implements SourceTransformTaskConfig {
+abstract public class CompileTaskConfig implements SourceTransformTaskConfig {
 
     private final LanguageTransform<? extends LanguageSourceSet, ObjectFile> languageTransform;
     private final Class<? extends DefaultTask> taskType;
@@ -102,35 +97,5 @@ public class CompileTaskConfig implements SourceTransformTaskConfig {
         }
     }
 
-    protected void configureCompileTask(AbstractNativeCompileTask task, final NativeBinarySpecInternal binary, final LanguageSourceSetInternal sourceSet) {
-        task.setDescription(String.format("Compiles the %s of %s", sourceSet, binary));
-
-        task.source(sourceSet.getSource());
-
-        final Project project = task.getProject();
-        task.setObjectFileDir(project.file(String.valueOf(project.getBuildDir()) + "/objs/" + binary.getNamingScheme().getOutputDirectoryBase() + "/" + sourceSet.getFullName()));
-
-        // If this task uses a pre-compiled header
-        if (sourceSet instanceof DependentSourceSet && !((DependentSourceSet) sourceSet).getPreCompiledHeaders().isEmpty()) {
-            task.setPrefixHeaderFile(((DependentSourceSet)sourceSet).getPrefixHeaderFile());
-            task.setPreCompiledHeaders(((DependentSourceSet) sourceSet).getPreCompiledHeaders());
-            task.preCompiledHeaderInclude(new Callable<FileCollection>() {
-                public FileCollection call() {
-                    Set<AbstractNativePCHCompileTask> pchTasks = binary.getTasks().withType(AbstractNativePCHCompileTask.class).matching(new Spec<AbstractNativePCHCompileTask>() {
-                        @Override
-                        public boolean isSatisfiedBy(AbstractNativePCHCompileTask pchCompileTask) {
-                            return ((DependentSourceSet) sourceSet).getPrefixHeaderFile().equals(pchCompileTask.getPrefixHeaderFile());
-                        }
-                    });
-                    if (!pchTasks.isEmpty()) {
-                        return pchTasks.iterator().next().getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.pch", "**/*.gch"));
-                    } else {
-                        return new SimpleFileCollection();
-                    }
-                }
-            });
-        }
-
-        binary.binaryInputs(task.getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.obj", "**/*.o")));
-    }
+    abstract void configureCompileTask(AbstractNativeCompileTask task, final NativeBinarySpecInternal binary, final LanguageSourceSetInternal sourceSet);
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/PCHCompileTaskConfig.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/PCHCompileTaskConfig.java
index 7a1da86..636077b 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/PCHCompileTaskConfig.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/PCHCompileTaskConfig.java
@@ -19,14 +19,19 @@ package org.gradle.language.nativeplatform.internal;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Project;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.language.base.internal.LanguageSourceSetInternal;
 import org.gradle.language.base.internal.registry.LanguageTransform;
-import org.gradle.language.nativeplatform.DependentSourceSet;
 import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
 import org.gradle.nativeplatform.ObjectFile;
 import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
 import org.gradle.nativeplatform.tasks.PrefixHeaderFileGenerateTask;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
+
+import java.io.File;
+import java.util.Set;
+import java.util.concurrent.Callable;
 
 public class PCHCompileTaskConfig extends CompileTaskConfig {
     public PCHCompileTaskConfig(LanguageTransform<? extends LanguageSourceSet, ObjectFile> languageTransform, Class<? extends DefaultTask> taskType) {
@@ -37,16 +42,21 @@ public class PCHCompileTaskConfig extends CompileTaskConfig {
     protected void configureCompileTask(AbstractNativeCompileTask task, final NativeBinarySpecInternal binary, final LanguageSourceSetInternal languageSourceSet) {
         // Note that the sourceSet is the sourceSet this pre-compiled header will be used with - it's not an
         // input sourceSet to the compile task.
-        final DependentSourceSet sourceSet = (DependentSourceSet) languageSourceSet;
+        final DependentSourceSetInternal sourceSet = (DependentSourceSetInternal) languageSourceSet;
 
         task.setDescription(String.format("Compiles a pre-compiled header for the %s of %s", sourceSet, binary));
 
+        // Add the source of the source set to the include paths to resolve any headers that may be in source directories
+        task.includes(new Callable<Set<File>>() {
+            public Set<File> call() throws Exception {
+                return sourceSet.getSource().getSrcDirs();
+            }
+        });
+
         final Project project = task.getProject();
-        task.setPrefixHeaderFile(sourceSet.getPrefixHeaderFile());
-        task.setPreCompiledHeaders(sourceSet.getPreCompiledHeaders());
         task.source(sourceSet.getPrefixHeaderFile());
 
-        task.setObjectFileDir(project.file(String.valueOf(project.getBuildDir()) + "/objs/" + binary.getNamingScheme().getOutputDirectoryBase() + "/" + languageSourceSet.getFullName() + "PreCompiledHeader"));
+        task.setObjectFileDir(project.file(String.valueOf(project.getBuildDir()) + "/objs/" + binary.getNamingScheme().getOutputDirectoryBase() + "/" + languageSourceSet.getFullName() + "PCH"));
 
         task.dependsOn(project.getTasks().withType(PrefixHeaderFileGenerateTask.class).matching(new Spec<PrefixHeaderFileGenerateTask>() {
             @Override
@@ -54,5 +64,12 @@ public class PCHCompileTaskConfig extends CompileTaskConfig {
                 return prefixHeaderFileGenerateTask.getPrefixHeaderFile().equals(sourceSet.getPrefixHeaderFile());
             }
         }));
+
+        // This is so that VisualCpp has the object file of the generated source file available at link time
+        binary.binaryInputs(task.getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.obj", "**/*.o")));
+
+        PreCompiledHeader pch = binary.getPrefixFileToPCH().get(sourceSet.getPrefixHeaderFile());
+        pch.setPchObjects(task.getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.pch", "**/*.gch")));
+        pch.builtBy(task);
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/SourceCompileTaskConfig.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/SourceCompileTaskConfig.java
new file mode 100644
index 0000000..cdbfc3a
--- /dev/null
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/SourceCompileTaskConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.LanguageSourceSetInternal;
+import org.gradle.language.base.internal.registry.LanguageTransform;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
+import org.gradle.nativeplatform.ObjectFile;
+import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
+
+public class SourceCompileTaskConfig extends CompileTaskConfig {
+    public SourceCompileTaskConfig(LanguageTransform<? extends LanguageSourceSet, ObjectFile> languageTransform, Class<? extends DefaultTask> taskType) {
+        super(languageTransform, taskType);
+    }
+
+    protected void configureCompileTask(AbstractNativeCompileTask abstractTask, final NativeBinarySpecInternal binary, final LanguageSourceSetInternal sourceSet) {
+        AbstractNativeSourceCompileTask task = (AbstractNativeSourceCompileTask) abstractTask;
+
+        task.setDescription(String.format("Compiles the %s of %s", sourceSet, binary));
+
+        task.source(sourceSet.getSource());
+
+        final Project project = task.getProject();
+        task.setObjectFileDir(project.file(String.valueOf(project.getBuildDir()) + "/objs/" + binary.getNamingScheme().getOutputDirectoryBase() + "/" + sourceSet.getFullName()));
+
+        // If this task uses a pre-compiled header
+        if (sourceSet instanceof DependentSourceSetInternal && ((DependentSourceSetInternal) sourceSet).getPreCompiledHeader() != null) {
+            final DependentSourceSetInternal dependentSourceSet = (DependentSourceSetInternal)sourceSet;
+            PreCompiledHeader pch = binary.getPrefixFileToPCH().get(dependentSourceSet.getPrefixHeaderFile());
+            pch.setPrefixHeaderFile(dependentSourceSet.getPrefixHeaderFile());
+            pch.setIncludeString(dependentSourceSet.getPreCompiledHeader());
+            task.setPreCompiledHeader(pch);
+        }
+
+        binary.binaryInputs(task.getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.obj", "**/*.o")));
+    }
+}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationFileState.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationFileState.java
index 94ee4ba..c9cc18c 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationFileState.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationFileState.java
@@ -16,6 +16,7 @@
 package org.gradle.language.nativeplatform.internal.incremental;
 
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultSourceIncludes;
 
 import java.io.Serializable;
 import java.util.HashSet;
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializer.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializer.java
index c8a27f7..e441048 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializer.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializer.java
@@ -16,7 +16,11 @@
 package org.gradle.language.nativeplatform.internal.incremental;
 
 import org.gradle.internal.serialize.*;
+import org.gradle.language.nativeplatform.internal.Include;
+import org.gradle.language.nativeplatform.internal.IncludeType;
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultInclude;
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultSourceIncludes;
 
 import java.io.File;
 import java.util.Set;
@@ -108,21 +112,38 @@ public class CompilationStateSerializer implements Serializer<CompilationState>
     }
 
     private class SourceIncludesSerializer implements Serializer<SourceIncludes> {
-        private final Serializer<String> stringSerializer = serializerFactory.getSerializerFor(String.class);
-        private final ListSerializer<String> stringListSerializer = new ListSerializer<String>(stringSerializer);
+        private final Serializer<Include> includeSerializer = new IncludeSerializer();
+        private final ListSerializer<Include> includeListSerializer = new ListSerializer<Include>(includeSerializer);
 
         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));
+            DefaultSourceIncludes sourceIncludes = new DefaultSourceIncludes();
+            sourceIncludes.addAll(includeListSerializer.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());
+            includeListSerializer.write(encoder, value.getIncludesAndImports());
+        }
+    }
+
+    private class IncludeSerializer implements Serializer<Include> {
+        private final Serializer<String> stringSerializer = serializerFactory.getSerializerFor(String.class);
+        private final Serializer<Boolean> booleanSerializer = serializerFactory.getSerializerFor(Boolean.class);
+        private final Serializer<IncludeType> enumSerializer = serializerFactory.getSerializerFor(IncludeType.class);
+
+        @Override
+        public Include read(Decoder decoder) throws Exception {
+            String value = stringSerializer.read(decoder);
+            boolean isImport = booleanSerializer.read(decoder);
+            IncludeType type = enumSerializer.read(decoder);
+            return new DefaultInclude(value, isImport, type);
+        }
+
+        @Override
+        public void write(Encoder encoder, Include value) throws Exception {
+            stringSerializer.write(encoder, value.getValue());
+            booleanSerializer.write(encoder, value.isImport());
+            enumSerializer.write(encoder, value.getType());
         }
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultIncrementalCompilation.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultIncrementalCompilation.java
index af778b1..7ab8b7c 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultIncrementalCompilation.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultIncrementalCompilation.java
@@ -15,21 +15,16 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental;
 
-import org.gradle.language.nativeplatform.internal.SourceIncludes;
-
 import java.io.File;
 import java.util.List;
-import java.util.Map;
 
 public class DefaultIncrementalCompilation implements IncrementalCompilation {
     private final List<File> recompile;
     private final List<File> removed;
-    private Map<File, SourceIncludes> sourceIncludes;
     private CompilationState finalState;
 
-    public DefaultIncrementalCompilation(CompilationState finalState, List<File> recompile, List<File> removed, Map<File, SourceIncludes> sourceIncludes) {
+    public DefaultIncrementalCompilation(CompilationState finalState, List<File> recompile, List<File> removed) {
         this.finalState = finalState;
-        this.sourceIncludes = sourceIncludes;
         this.recompile = recompile;
         this.removed = removed;
     }
@@ -42,10 +37,6 @@ public class DefaultIncrementalCompilation implements IncrementalCompilation {
         return removed;
     }
 
-    public Map<File, SourceIncludes> getSourceFileIncludes() {
-        return sourceIncludes;
-    }
-
     public CompilationState getFinalState() {
         return finalState;
     }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludes.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludes.java
deleted file mode 100644
index d7f7c53..0000000
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludes.java
+++ /dev/null
@@ -1,81 +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.language.nativeplatform.internal.incremental;
-
-import org.gradle.language.nativeplatform.internal.SourceIncludes;
-
-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/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParser.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParser.java
index 911c96c..2f99c7e 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParser.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParser.java
@@ -18,6 +18,7 @@ package org.gradle.language.nativeplatform.internal.incremental;
 
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
 import org.gradle.language.nativeplatform.internal.incremental.sourceparser.CSourceParser;
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultSourceIncludes;
 
 import java.io.File;
 
@@ -31,14 +32,14 @@ public class DefaultSourceIncludesParser implements SourceIncludesParser {
     }
 
     public SourceIncludes parseIncludes(File sourceFile) {
-        CSourceParser.SourceDetails sourceDetails = sourceParser.parseSource(sourceFile);
-        DefaultSourceIncludes defaultIncludes = new DefaultSourceIncludes();
-        defaultIncludes.addAll(sourceDetails.getIncludes());
+        SourceIncludes parsedIncludes = sourceParser.parseSource(sourceFile);
         if (importAware) {
-            defaultIncludes.addAll(sourceDetails.getImports());
+            return parsedIncludes;
+        } else {
+            DefaultSourceIncludes includesOnly = new DefaultSourceIncludes();
+            includesOnly.addAll(parsedIncludes.getIncludesOnly());
+            return includesOnly;
         }
-
-        return defaultIncludes;
     }
 
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolver.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolver.java
index f556654..fb37ac0 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolver.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolver.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental;
 
+import org.gradle.language.nativeplatform.internal.Include;
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
 import org.gradle.util.GFileUtils;
 
@@ -36,7 +37,7 @@ public class DefaultSourceIncludesResolver implements SourceIncludesResolver {
         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));
+            dependencies.add(new ResolvedInclude(includes.getMacroIncludes().get(0).getValue(), null));
         }
 
         return dependencies;
@@ -49,9 +50,9 @@ public class DefaultSourceIncludesResolver implements SourceIncludesResolver {
         return quotedSearchPath;
     }
 
-    private void searchForDependencies(Set<ResolvedInclude> dependencies, List<File> searchPath, List<String> includes) {
-        for (String include : includes) {
-            searchForDependency(dependencies, searchPath, include);
+    private void searchForDependencies(Set<ResolvedInclude> dependencies, List<File> searchPath, List<Include> includes) {
+        for (Include include : includes) {
+            searchForDependency(dependencies, searchPath, include.getValue());
         }
     }
 
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompilation.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompilation.java
index 238e660..4131642 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompilation.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompilation.java
@@ -15,18 +15,13 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental;
 
-import org.gradle.language.nativeplatform.internal.SourceIncludes;
-
 import java.io.File;
 import java.util.List;
-import java.util.Map;
 
 public interface IncrementalCompilation {
     List<File> getRecompile();
 
     List<File> getRemoved();
 
-    Map<File, SourceIncludes> getSourceFileIncludes();
-
     CompilationState getFinalState();
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessor.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessor.java
index a2b8545..9a30ce0 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessor.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessor.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental;
 
-import com.google.common.collect.Maps;
 import org.gradle.api.internal.changedetection.state.FileSnapshotter;
 import org.gradle.cache.PersistentStateCache;
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
@@ -49,15 +48,7 @@ public class IncrementalCompileProcessor {
             result.processSource(sourceFile);
         }
 
-        return new DefaultIncrementalCompilation(result.current, result.getModifiedSources(), result.getRemovedSources(), mapIncludes(sourceFiles, result.current));
-    }
-
-    private Map<File, SourceIncludes> mapIncludes(Collection<File> files, CompilationState compilationState) {
-        Map<File, SourceIncludes> map = Maps.newHashMap();
-        for (File file : files) {
-            map.put(file, compilationState.getState(file).getSourceIncludes());
-        }
-        return map;
+        return new DefaultIncrementalCompilation(result.current, result.getModifiedSources(), result.getRemovedSources());
     }
 
     private class IncrementalCompileFiles {
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java
index ed3578f..9c28929 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental;
 
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.changedetection.state.FileSnapshotter;
 import org.gradle.api.internal.changedetection.state.TaskArtifactStateCacheAccess;
@@ -24,6 +25,7 @@ import org.gradle.cache.PersistentStateCache;
 import org.gradle.internal.Factory;
 import org.gradle.language.base.internal.compile.Compiler;
 import org.gradle.language.base.internal.tasks.SimpleStaleClassCleaner;
+import org.gradle.language.nativeplatform.internal.SourceIncludes;
 import org.gradle.language.nativeplatform.internal.incremental.sourceparser.CSourceParser;
 import org.gradle.language.nativeplatform.internal.incremental.sourceparser.RegexBackedCSourceParser;
 import org.gradle.nativeplatform.toolchain.Clang;
@@ -33,6 +35,8 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
+import java.util.Collection;
+import java.util.Map;
 
 public class IncrementalNativeCompiler<T extends NativeCompileSpec> implements Compiler<T> {
     private final Compiler<T> delegateCompiler;
@@ -61,12 +65,12 @@ public class IncrementalNativeCompiler<T extends NativeCompileSpec> implements C
                 DefaultSourceIncludesParser sourceIncludesParser = new DefaultSourceIncludesParser(sourceParser, importsAreIncludes);
                 IncrementalCompileProcessor processor = createProcessor(compileStateCache, sourceIncludesParser, spec.getIncludeRoots());
                 // TODO - do not hold the lock while processing the source files - this prevents other tasks from executing concurrently
-                IncrementalCompilation incrementalCompilation = processor.processSourceFiles(spec.getSourceFiles());
-                spec.setSourceFileIncludes(incrementalCompilation.getSourceFileIncludes());
-                return incrementalCompilation;
+                return processor.processSourceFiles(spec.getSourceFiles());
             }
         });
 
+        spec.setSourceFileIncludes(mapIncludes(spec.getSourceFiles(), compilation.getFinalState()));
+
         WorkResult workResult;
         if (spec.isIncrementalCompile()) {
             workResult = doIncrementalCompile(compilation, spec);
@@ -84,6 +88,15 @@ public class IncrementalNativeCompiler<T extends NativeCompileSpec> implements C
         return workResult;
     }
 
+    private Map<File, SourceIncludes> mapIncludes(Collection<File> files, final CompilationState compilationState) {
+        return CollectionUtils.collectMapValues(files, new Transformer<SourceIncludes, File>() {
+            @Override
+            public SourceIncludes transform(File file) {
+                return compilationState.getState(file).getSourceIncludes();
+            }
+        });
+    }
+
     protected WorkResult doIncrementalCompile(IncrementalCompilation compilation, T spec) {
         // Determine the actual sources to clean/compile
         spec.setSourceFiles(compilation.getRecompile());
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/CSourceParser.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/CSourceParser.java
index 7908e86..674fb30 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/CSourceParser.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/CSourceParser.java
@@ -16,18 +16,15 @@
 
 package org.gradle.language.nativeplatform.internal.incremental.sourceparser;
 
+import org.gradle.language.nativeplatform.internal.SourceIncludes;
+
 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);
+    SourceIncludes parseSource(File sourceFile);
 
-    interface SourceDetails {
-        List<String> getIncludes();
-        List<String> getImports();
-    }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultInclude.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultInclude.java
new file mode 100644
index 0000000..76ef469
--- /dev/null
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultInclude.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal.incremental.sourceparser;
+
+import org.gradle.language.nativeplatform.internal.Include;
+import org.gradle.language.nativeplatform.internal.IncludeType;
+
+public class DefaultInclude implements Include {
+    private final String value;
+    private final boolean isImport;
+    private final IncludeType type;
+
+    public DefaultInclude(String value, boolean isImport, IncludeType type) {
+        if (value == null) {
+            throw new IllegalArgumentException("value cannot be null");
+        }
+        if (type == null) {
+            throw new IllegalArgumentException("type cannot be null");
+        }
+        this.value = value;
+        this.isImport = isImport;
+        this.type = type;
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean isImport() {
+        return isImport;
+    }
+
+    @Override
+    public IncludeType getType() {
+        return type;
+    }
+
+    public static Include parse(String value, boolean isImport) {
+        if (value.startsWith("<") && value.endsWith(">")) {
+            return new DefaultInclude(strip(value), isImport, IncludeType.SYSTEM);
+        } else if (value.startsWith("\"") && value.endsWith("\"")) {
+            return new DefaultInclude(strip(value), isImport, IncludeType.QUOTED);
+        } else {
+            return new DefaultInclude(value, isImport, IncludeType.MACRO);
+        }
+    }
+
+    private static String strip(String include) {
+        return include.substring(1, include.length() - 1);
+    }
+
+    @Override
+    public String toString() {
+        return value.concat(":").concat(type.toString()).concat(":").concat(String.valueOf(isImport));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultInclude that = (DefaultInclude) o;
+
+        if (isImport != that.isImport) {
+            return false;
+        }
+        if (type != that.type) {
+            return false;
+        }
+        if (value != null ? !value.equals(that.value) : that.value != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = value.hashCode();
+        result = 31 * result + (isImport ? 1 : 0);
+        result = 31 * result + type.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludes.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludes.java
new file mode 100644
index 0000000..f46f284
--- /dev/null
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludes.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.language.nativeplatform.internal.incremental.sourceparser;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.language.nativeplatform.internal.Include;
+import org.gradle.language.nativeplatform.internal.IncludeType;
+import org.gradle.language.nativeplatform.internal.SourceIncludes;
+import org.gradle.util.CollectionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultSourceIncludes implements SourceIncludes, Serializable {
+    private final List<Include> allIncludes = new ArrayList<Include>();
+
+    public void addAll(List<Include> includes) {
+        this.allIncludes.addAll(includes);
+    }
+
+    public List<Include> getQuotedIncludes() {
+        return CollectionUtils.filter(allIncludes, new Spec<Include>() {
+            @Override
+            public boolean isSatisfiedBy(Include element) {
+                return element.getType() == IncludeType.QUOTED;
+            }
+        });
+    }
+
+    public List<Include> getSystemIncludes() {
+        return CollectionUtils.filter(allIncludes, new Spec<Include>() {
+            @Override
+            public boolean isSatisfiedBy(Include element) {
+                return element.getType() == IncludeType.SYSTEM;
+            }
+        });
+    }
+
+    public List<Include> getMacroIncludes() {
+        return CollectionUtils.filter(allIncludes, new Spec<Include>() {
+            @Override
+            public boolean isSatisfiedBy(Include element) {
+                return element.getType() == IncludeType.MACRO;
+            }
+        });
+    }
+
+    public List<Include> getIncludesAndImports() {
+        return allIncludes;
+    }
+
+    public List<Include> getIncludesOnly() {
+        return CollectionUtils.filter(allIncludes, new Spec<Include>() {
+            @Override
+            public boolean isSatisfiedBy(Include element) {
+                return !element.isImport();
+            }
+        });
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultSourceIncludes that = (DefaultSourceIncludes) o;
+
+        if (!allIncludes.equals(that.allIncludes)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return allIncludes.hashCode();
+    }
+}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParser.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParser.java
index 8782c10..e91cac0 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParser.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParser.java
@@ -16,11 +16,13 @@
 
 package org.gradle.language.nativeplatform.internal.incremental.sourceparser;
 
+import com.google.common.collect.Lists;
 import org.apache.commons.io.IOUtils;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.language.nativeplatform.internal.Include;
+import org.gradle.language.nativeplatform.internal.SourceIncludes;
 
 import java.io.*;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -33,13 +35,14 @@ public class RegexBackedCSourceParser implements CSourceParser {
         this.includePattern = Pattern.compile(INCLUDE_IMPORT_PATTERN, Pattern.CASE_INSENSITIVE);
     }
 
-    public SourceDetails parseSource(File sourceFile) {
-        DefaultSourceDetails sourceDetails = new DefaultSourceDetails();
-        parseFile(sourceFile, sourceDetails);
-        return sourceDetails;
+    public SourceIncludes parseSource(File sourceFile) {
+        DefaultSourceIncludes sourceIncludes = new DefaultSourceIncludes();
+        sourceIncludes.addAll(parseFile(sourceFile));
+        return sourceIncludes;
     }
 
-    private void parseFile(File file, DefaultSourceDetails sourceDetails) {
+    private List<Include> parseFile(File file) {
+        List<Include> includes = Lists.newArrayList();
         try {
             BufferedReader bf = new BufferedReader(new PreprocessingReader(new BufferedReader(new FileReader(file))));
 
@@ -51,11 +54,8 @@ public class RegexBackedCSourceParser implements CSourceParser {
                     if (m.matches()) {
                         boolean isImport = "import".equals(m.group(1));
                         String value = m.group(2);
-                        if (isImport) {
-                            sourceDetails.getImports().add(value);
-                        } else {
-                            sourceDetails.getIncludes().add(value);
-                        }
+
+                        includes.add(DefaultInclude.parse(value, isImport));
                     }
                 }
             } finally {
@@ -64,18 +64,7 @@ public class RegexBackedCSourceParser implements CSourceParser {
         } 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;
-        }
+        return includes;
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java
index 1ed42d4..fb49a1c 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java
@@ -32,14 +32,12 @@ import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
 import org.gradle.nativeplatform.toolchain.NativeToolChain;
 import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
-import org.gradle.nativeplatform.toolchain.internal.PCHObjectDirectoryGeneratorUtil;
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
 
 import javax.inject.Inject;
 import java.io.File;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Callable;
 
 /**
@@ -55,14 +53,10 @@ public abstract class AbstractNativeCompileTask extends DefaultTask {
     private ConfigurableFileCollection source;
     private Map<String, String> macros;
     private List<String> compilerArgs;
-    private File prefixHeaderFile;
-    private Set<String> preCompiledHeaders;
-    private ConfigurableFileCollection preCompiledHeaderInclude;
 
     public AbstractNativeCompileTask() {
         includes = getProject().files();
         source = getProject().files();
-        preCompiledHeaderInclude = getProject().files();
         getInputs().property("outputType", new Callable<String>() {
             @Override
             public String call() throws Exception {
@@ -96,17 +90,15 @@ public abstract class AbstractNativeCompileTask extends DefaultTask {
         spec.setIncrementalCompile(inputs.isIncremental());
         spec.setOperationLogger(operationLogger);
 
-        if (!preCompiledHeaderInclude.isEmpty()) {
-            File pchDir = PCHObjectDirectoryGeneratorUtil.generatePCHObjectDirectory(spec.getTempDir(), getPrefixHeaderFile(), preCompiledHeaderInclude.getSingleFile());
-            spec.setPrefixHeaderFile(new File(pchDir, prefixHeaderFile.getName()));
-            spec.setPreCompiledHeaderObjectFile(new File(pchDir, preCompiledHeaderInclude.getSingleFile().getName()));
-            spec.setPreCompiledHeaders(getPreCompiledHeaders());
-        }
+        configureSpec(spec);
 
         PlatformToolProvider platformToolProvider = toolChain.select(targetPlatform);
         setDidWork(doCompile(spec, platformToolProvider).getDidWork());
     }
 
+    protected void configureSpec(NativeCompileSpec spec) {
+    }
+
     private <T extends NativeCompileSpec> WorkResult doCompile(T spec, PlatformToolProvider platformToolProvider) {
         Class<T> specType = Cast.uncheckedCast(spec.getClass());
         Compiler<T> baseCompiler = platformToolProvider.newCompiler(specType);
@@ -216,38 +208,4 @@ public abstract class AbstractNativeCompileTask extends DefaultTask {
     public void setCompilerArgs(List<String> compilerArgs) {
         this.compilerArgs = compilerArgs;
     }
-
-    /**
-     * Returns the pre-compiled header file to be used during compilation
-     */
-    public File getPrefixHeaderFile() {
-        return prefixHeaderFile;
-    }
-
-    public void setPrefixHeaderFile(File prefixHeaderFile) {
-        this.prefixHeaderFile = prefixHeaderFile;
-    }
-
-    /**
-     * Returns the pre-compiled header object file to be used during compilation
-     */
-    @InputFiles
-    public FileCollection getPreCompiledHeaderInclude() {
-        return preCompiledHeaderInclude;
-    }
-
-    /**
-     * Set the pre-compiled header the compiler should use.
-     */
-    public void preCompiledHeaderInclude(Object preCompiledHeader) {
-        preCompiledHeaderInclude.from(preCompiledHeader);
-    }
-
-    public Set<String> getPreCompiledHeaders() {
-        return preCompiledHeaders;
-    }
-
-    public void setPreCompiledHeaders(Set<String> preCompiledHeaders) {
-        this.preCompiledHeaders = preCompiledHeaders;
-    }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeSourceCompileTask.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeSourceCompileTask.java
new file mode 100644
index 0000000..dd8d077
--- /dev/null
+++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeSourceCompileTask.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.tasks;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultInclude;
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
+import org.gradle.nativeplatform.toolchain.internal.PCHUtils;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
+
+import java.io.File;
+
+/**
+ * Compiles native source files into object files.
+ */
+ at Incubating
+abstract public class AbstractNativeSourceCompileTask extends AbstractNativeCompileTask {
+    private PreCompiledHeader preCompiledHeader;
+
+    public AbstractNativeSourceCompileTask() {
+        super();
+    }
+
+    @Override
+    protected void configureSpec(NativeCompileSpec spec) {
+        super.configureSpec(spec);
+        if (preCompiledHeader != null) {
+            File pchObjectFile = preCompiledHeader.getObjectFile();
+            File pchDir = PCHUtils.generatePCHObjectDirectory(spec.getTempDir(), preCompiledHeader.getPrefixHeaderFile(), pchObjectFile);
+            spec.setPrefixHeaderFile(new File(pchDir, preCompiledHeader.getPrefixHeaderFile().getName()));
+            spec.setPreCompiledHeaderObjectFile(new File(pchDir, pchObjectFile.getName()));
+            spec.setPreCompiledHeader(DefaultInclude.parse(preCompiledHeader.getIncludeString(), true).getValue());
+        }
+    }
+
+    /**
+     * Returns the pre-compiled header to be used during compilation
+     */
+    @Nested @Optional
+    public PreCompiledHeader getPreCompiledHeader() {
+        return preCompiledHeader;
+    }
+
+    public void setPreCompiledHeader(PreCompiledHeader preCompiledHeader) {
+        this.preCompiledHeader = preCompiledHeader;
+    }
+}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPCHPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPCHPlugin.java
deleted file mode 100644
index a78f6d8..0000000
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPCHPlugin.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.Maps;
-import org.gradle.language.base.internal.SourceTransformTaskConfig;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
-import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
-import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
-import org.gradle.language.objectivec.ObjectiveCSourceSet;
-import org.gradle.language.objectivec.tasks.ObjectiveCPreCompiledHeaderCompile;
-import org.gradle.model.Mutate;
-import org.gradle.model.RuleSource;
-import org.gradle.nativeplatform.internal.pch.PreCompiledHeaderTransformContainer;
-
-import java.util.Map;
-
-/**
- * Adds support for compiling Objective C pre-compiled headers.
- */
- at SuppressWarnings("UnusedDeclaration")
-public class ObjectiveCLangPCHPlugin extends RuleSource {
-    @Mutate
-    void registerPreCompiledHeaderTask(PreCompiledHeaderTransformContainer pchTransformContainer) {
-        pchTransformContainer.add(new ObjectiveCPCH());
-    }
-
-    private static class ObjectiveCPCH extends NativeLanguageTransform<ObjectiveCSourceSet> {
-        public Class<ObjectiveCSourceSet> getSourceSetType() {
-            return ObjectiveCSourceSet.class;
-        }
-
-        public Map<String, Class<?>> getBinaryTools() {
-            Map<String, Class<?>> tools = Maps.newLinkedHashMap();
-            tools.put("objcCompiler", DefaultPreprocessingTool.class);
-            return tools;
-        }
-
-        @Override
-        public SourceTransformTaskConfig getTransformTask() {
-            return new PCHCompileTaskConfig(this, ObjectiveCPreCompiledHeaderCompile.class);
-        }
-    }
-}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.java
index 31f11a3..9d1eaf5 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCLangPlugin.java
@@ -23,14 +23,17 @@ import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.internal.SourceTransformTaskConfig;
 import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
-import org.gradle.language.nativeplatform.internal.CompileTaskConfig;
 import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
+import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
+import org.gradle.language.nativeplatform.internal.SourceCompileTaskConfig;
 import org.gradle.language.objectivec.ObjectiveCSourceSet;
 import org.gradle.language.objectivec.internal.DefaultObjectiveCSourceSet;
 import org.gradle.language.objectivec.tasks.ObjectiveCCompile;
+import org.gradle.language.objectivec.tasks.ObjectiveCPreCompiledHeaderCompile;
 import org.gradle.model.Mutate;
 import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.internal.pch.PchEnabledLanguageTransform;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
 
@@ -59,7 +62,7 @@ public class ObjectiveCLangPlugin implements Plugin<Project> {
         }
     }
 
-    private static class ObjectiveC extends NativeLanguageTransform<ObjectiveCSourceSet> {
+    private static class ObjectiveC extends NativeLanguageTransform<ObjectiveCSourceSet> implements PchEnabledLanguageTransform<ObjectiveCSourceSet> {
         public Class<ObjectiveCSourceSet> getSourceSetType() {
             return ObjectiveCSourceSet.class;
         }
@@ -71,7 +74,11 @@ public class ObjectiveCLangPlugin implements Plugin<Project> {
         }
 
         public SourceTransformTaskConfig getTransformTask() {
-            return new CompileTaskConfig(this, ObjectiveCCompile.class);
+            return new SourceCompileTaskConfig(this, ObjectiveCCompile.class);
+        }
+
+        public SourceTransformTaskConfig getPchTransformTask() {
+            return new PCHCompileTaskConfig(this, ObjectiveCPreCompiledHeaderCompile.class);
         }
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCPlugin.java
index 532ab77..dfc4f07 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/plugins/ObjectiveCPlugin.java
@@ -33,7 +33,6 @@ public class ObjectiveCPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPluginManager().apply(NativeComponentPlugin.class);
         project.getPluginManager().apply(ObjectiveCLangPlugin.class);
-        project.getPluginManager().apply(ObjectiveCLangPCHPlugin.class);
     }
 
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/tasks/ObjectiveCCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/tasks/ObjectiveCCompile.java
index 4d93f21..d6643bb 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivec/tasks/ObjectiveCCompile.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivec/tasks/ObjectiveCCompile.java
@@ -17,7 +17,7 @@ package org.gradle.language.objectivec.tasks;
 
 import org.gradle.api.Incubating;
 import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
 import org.gradle.language.objectivec.internal.DefaultObjectiveCCompileSpec;
 import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 
@@ -26,7 +26,7 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
  */
 @Incubating
 @ParallelizableTask
-public class ObjectiveCCompile extends AbstractNativeCompileTask {
+public class ObjectiveCCompile extends AbstractNativeSourceCompileTask {
     @Override
     protected NativeCompileSpec createCompileSpec() {
         return new DefaultObjectiveCCompileSpec();
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPCHPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPCHPlugin.java
deleted file mode 100644
index 1b489e2..0000000
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPCHPlugin.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.Maps;
-import org.gradle.language.base.internal.SourceTransformTaskConfig;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
-import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
-import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
-import org.gradle.language.objectivecpp.ObjectiveCppSourceSet;
-import org.gradle.language.objectivecpp.tasks.ObjectiveCppPreCompiledHeaderCompile;
-import org.gradle.model.Mutate;
-import org.gradle.model.RuleSource;
-import org.gradle.nativeplatform.internal.pch.PreCompiledHeaderTransformContainer;
-
-import java.util.Map;
-
-/**
- * Adds support for compiling Objective C++ pre-compiled headers.
- */
- at SuppressWarnings("UnusedDeclaration")
-public class ObjectiveCppLangPCHPlugin extends RuleSource {
-    @Mutate
-    void registerPreCompiledHeaderTask(PreCompiledHeaderTransformContainer pchTransformContainer) {
-        pchTransformContainer.add(new ObjectiveCppPCH());
-    }
-
-    private static class ObjectiveCppPCH extends NativeLanguageTransform<ObjectiveCppSourceSet> {
-        public Class<ObjectiveCppSourceSet> getSourceSetType() {
-            return ObjectiveCppSourceSet.class;
-        }
-
-        public Map<String, Class<?>> getBinaryTools() {
-            Map<String, Class<?>> tools = Maps.newLinkedHashMap();
-            tools.put("objcppCompiler", DefaultPreprocessingTool.class);
-            return tools;
-        }
-
-        @Override
-        public SourceTransformTaskConfig getTransformTask() {
-            return new PCHCompileTaskConfig(this, ObjectiveCppPreCompiledHeaderCompile.class);
-        }
-    }
-}
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.java
index 09b3177..ec9dd07 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppLangPlugin.java
@@ -23,14 +23,17 @@ import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.internal.SourceTransformTaskConfig;
 import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
-import org.gradle.language.nativeplatform.internal.CompileTaskConfig;
 import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.language.nativeplatform.internal.NativeLanguageTransform;
+import org.gradle.language.nativeplatform.internal.PCHCompileTaskConfig;
+import org.gradle.language.nativeplatform.internal.SourceCompileTaskConfig;
 import org.gradle.language.objectivecpp.ObjectiveCppSourceSet;
 import org.gradle.language.objectivecpp.internal.DefaultObjectiveCppSourceSet;
 import org.gradle.language.objectivecpp.tasks.ObjectiveCppCompile;
+import org.gradle.language.objectivecpp.tasks.ObjectiveCppPreCompiledHeaderCompile;
 import org.gradle.model.Mutate;
 import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.internal.pch.PchEnabledLanguageTransform;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
 
@@ -59,7 +62,7 @@ public class ObjectiveCppLangPlugin implements Plugin<Project> {
         }
     }
 
-    private static class ObjectiveCpp extends NativeLanguageTransform<ObjectiveCppSourceSet> {
+    private static class ObjectiveCpp extends NativeLanguageTransform<ObjectiveCppSourceSet> implements PchEnabledLanguageTransform<ObjectiveCppSourceSet> {
         public Class<ObjectiveCppSourceSet> getSourceSetType() {
             return ObjectiveCppSourceSet.class;
         }
@@ -71,8 +74,11 @@ public class ObjectiveCppLangPlugin implements Plugin<Project> {
         }
 
         public SourceTransformTaskConfig getTransformTask() {
-            return new CompileTaskConfig(this, ObjectiveCppCompile.class);
+            return new SourceCompileTaskConfig(this, ObjectiveCppCompile.class);
         }
 
+        public SourceTransformTaskConfig getPchTransformTask() {
+            return new PCHCompileTaskConfig(this, ObjectiveCppPreCompiledHeaderCompile.class);
+        }
     }
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppPlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppPlugin.java
index 96a70d7..faac908 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppPlugin.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/plugins/ObjectiveCppPlugin.java
@@ -33,7 +33,6 @@ public class ObjectiveCppPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPluginManager().apply(NativeComponentPlugin.class);
         project.getPluginManager().apply(ObjectiveCppLangPlugin.class);
-        project.getPluginManager().apply(ObjectiveCppLangPCHPlugin.class);
     }
 
 }
diff --git a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompile.java
index 87cdc82..b7e213d 100644
--- a/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompile.java
+++ b/subprojects/language-native/src/main/java/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompile.java
@@ -17,7 +17,7 @@ package org.gradle.language.objectivecpp.tasks;
 
 import org.gradle.api.Incubating;
 import org.gradle.api.tasks.ParallelizableTask;
-import org.gradle.language.nativeplatform.tasks.AbstractNativeCompileTask;
+import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
 import org.gradle.language.objectivecpp.internal.DefaultObjectiveCppCompileSpec;
 import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
 
@@ -26,7 +26,7 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
  */
 @Incubating
 @ParallelizableTask
-public class ObjectiveCppCompile extends AbstractNativeCompileTask {
+public class ObjectiveCppCompile extends AbstractNativeSourceCompileTask {
     @Override
     protected NativeCompileSpec createCompileSpec() {
         return new DefaultObjectiveCppCompileSpec();
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/AbstractNativeComponentPluginTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/AbstractNativeComponentPluginTest.groovy
index 4ec23bc..997a1b9 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/AbstractNativeComponentPluginTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/AbstractNativeComponentPluginTest.groovy
@@ -23,10 +23,14 @@ import org.gradle.api.Task
 import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.base.LanguageSourceSet
+import org.gradle.model.ModelMap
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.NativeBinary
 import org.gradle.nativeplatform.NativeExecutableBinarySpec
 import org.gradle.nativeplatform.NativeExecutableSpec
 import org.gradle.nativeplatform.NativeLibrarySpec
+import org.gradle.platform.base.ComponentSpec
 import org.gradle.util.GFileUtils
 import org.gradle.util.TestUtil
 import spock.lang.Specification
@@ -42,6 +46,10 @@ abstract class AbstractNativeComponentPluginTest extends Specification {
 
     abstract String getPluginName();
 
+    ModelMap<ComponentSpec> realizeComponents() {
+        project.modelRegistry.realize(ModelPath.path("components"), ModelTypes.modelMap(ComponentSpec))
+    }
+
     def "creates source set with conventional locations for components"() {
         when:
         dsl {
@@ -56,9 +64,9 @@ abstract class AbstractNativeComponentPluginTest extends Specification {
         }
 
         then:
-        def components = project.componentSpecs
+        def components = realizeComponents()
         components.size() == 2
-        components*.name == ["exe", "lib"]
+        components.values()*.name == ["exe", "lib"]
 
         and:
         def exe = components.exe
@@ -113,14 +121,16 @@ abstract class AbstractNativeComponentPluginTest extends Specification {
             }
         }
 
+
         expect:
-        def exe = project.componentSpecs.exe
+        def components = realizeComponents()
+        def exe = components.exe
         with(exe.sources."$pluginName") {
             source.srcDirs*.name == ["d1", "d2"]
             exportedHeaders.srcDirs*.name == ["h1", "h2"]
         }
 
-        def lib = project.componentSpecs.lib
+        def lib = components.lib
         with(lib.sources."$pluginName") {
             source.srcDirs*.name == ["d3"]
             exportedHeaders.srcDirs*.name == ["h3"]
@@ -177,4 +187,4 @@ abstract class AbstractNativeComponentPluginTest extends Specification {
         project.tasks.realize()
         project.bindAllModelRules()
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerPluginTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerPluginTest.groovy
index 3e7c876..3019e6f 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerPluginTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/assembler/plugins/AssemblerPluginTest.groovy
@@ -21,7 +21,11 @@ import org.gradle.api.tasks.TaskDependencyMatchers
 import org.gradle.language.assembler.AssemblerSourceSet
 import org.gradle.language.assembler.tasks.Assemble
 import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.model.ModelMap
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.*
+import org.gradle.platform.base.ComponentSpec
 import org.gradle.util.GFileUtils
 import org.gradle.util.TestUtil
 import spock.lang.Specification
@@ -29,6 +33,10 @@ import spock.lang.Specification
 class AssemblerPluginTest extends Specification {
     final def project = TestUtil.createRootProject()
 
+    ModelMap<ComponentSpec> realizeComponents() {
+        project.modelRegistry.realize(ModelPath.path("components"), ModelTypes.modelMap(ComponentSpec))
+    }
+
     def "creates asm source set with conventional locations for components"() {
         when:
         dsl {
@@ -40,8 +48,10 @@ class AssemblerPluginTest extends Specification {
             }
         }
 
+
         then:
-        def exe = project.componentSpecs.exe
+        def components = realizeComponents()
+        def exe = components.exe
         exe.sources instanceof FunctionalSourceSet
         exe.sources.asm instanceof AssemblerSourceSet
         exe.sources.asm.source.srcDirs == [project.file("src/exe/asm")] as Set
@@ -70,7 +80,7 @@ class AssemblerPluginTest extends Specification {
         }
 
         expect:
-        project.componentSpecs.exe.sources.asm.source.srcDirs*.name == ["d1", "d2"]
+        realizeComponents().exe.sources.asm.source.srcDirs*.name == ["d1", "d2"]
     }
 
     def "creates assemble tasks for each non-empty executable source set "() {
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CCompileTest.groovy
index 45e57d0..b7a99ef 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CCompileTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CCompileTest.groovy
@@ -15,6 +15,8 @@
  */
 
 package org.gradle.language.c.tasks
+
+import org.gradle.api.internal.file.collections.SimpleFileCollection
 import org.gradle.language.base.internal.compile.Compiler
 import org.gradle.api.tasks.WorkResult
 import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
@@ -22,8 +24,8 @@ import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
 import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader
 import org.gradle.nativeplatform.toolchain.internal.compilespec.CCompileSpec
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CppCompileSpec
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TestUtil
 import spock.lang.Specification
@@ -34,11 +36,13 @@ class CCompileTest extends Specification {
     def toolChain = Mock(NativeToolChainInternal)
     def platform = Mock(NativePlatformInternal)
     def platformToolChain = Mock(PlatformToolProvider)
-    Compiler<CppCompileSpec> cCompiler = Mock(Compiler)
+    Compiler<CCompileSpec> cCompiler = Mock(Compiler)
+    def pch = Mock(PreCompiledHeader)
 
     def "executes using the C Compiler"() {
         def sourceFile = testDir.createFile("sourceFile")
         def result = Mock(WorkResult)
+
         when:
         cCompile.toolChain = toolChain
         cCompile.targetPlatform = platform
@@ -46,6 +50,7 @@ class CCompileTest extends Specification {
         cCompile.macros = [def: "value"]
         cCompile.objectFileDir = testDir.file("outputFile")
         cCompile.source sourceFile
+        cCompile.setPreCompiledHeader pch
         cCompile.execute()
 
         then:
@@ -54,12 +59,19 @@ class CCompileTest extends Specification {
         platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
         1 * toolChain.select(platform) >> platformToolChain
         1 * platformToolChain.newCompiler({CCompileSpec.class.isAssignableFrom(it)}) >> cCompiler
+        1 * pch.objectFile >> testDir.file("pchObjectFile").createFile()
+        1 * pch.includeString >> "header"
+        2 * pch.prefixHeaderFile >> testDir.file("prefixHeader").createFile()
+        2 * pch.pchObjects >> new SimpleFileCollection()
         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"
+            assert spec.preCompiledHeader == "header"
+            assert spec.prefixHeaderFile.name == "prefixHeader"
+            assert spec.preCompiledHeaderObjectFile.name == "pchObjectFile"
             true
         }) >> result
         1 * result.didWork >> true
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CPreCompiledHeaderCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CPreCompiledHeaderCompileTest.groovy
new file mode 100644
index 0000000..9930922
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/c/tasks/CPreCompiledHeaderCompileTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class CPreCompiledHeaderCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    CPreCompiledHeaderCompile cPCHCompile = TestUtil.createTask(CPreCompiledHeaderCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<CPCHCompileSpec> cPCHCompiler = Mock(Compiler)
+
+    def "executes using the C PCH Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        cPCHCompile.toolChain = toolChain
+        cPCHCompile.targetPlatform = platform
+        cPCHCompile.compilerArgs = ["arg"]
+        cPCHCompile.macros = [def: "value"]
+        cPCHCompile.objectFileDir = testDir.file("outputFile")
+        cPCHCompile.source sourceFile
+        cPCHCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "c"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({CPCHCompileSpec.class.isAssignableFrom(it)}) >> cPCHCompiler
+        1 * cPCHCompiler.execute({ CPCHCompileSpec 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:
+        cPCHCompile.didWork
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppCompileTest.groovy
index 6b29bb7..efee1e8 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppCompileTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppCompileTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.language.cpp.tasks
 
+import org.gradle.api.internal.file.collections.SimpleFileCollection
 import org.gradle.api.tasks.WorkResult
 import org.gradle.language.base.internal.compile.Compiler
 import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
@@ -23,6 +24,7 @@ import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
 import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader
 import org.gradle.nativeplatform.toolchain.internal.compilespec.CppCompileSpec
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TestUtil
@@ -35,6 +37,7 @@ class CppCompileTest extends Specification {
     def platform = Mock(NativePlatformInternal)
     def platformToolChain = Mock(PlatformToolProvider)
     Compiler<CppCompileSpec> cppCompiler = Mock(Compiler)
+    def pch = Mock(PreCompiledHeader)
 
     def "executes using the CppCompiler"() {
         def sourceFile = testDir.createFile("sourceFile")
@@ -46,6 +49,7 @@ class CppCompileTest extends Specification {
         cppCompile.macros = [def: "value"]
         cppCompile.objectFileDir = testDir.file("outputFile")
         cppCompile.source sourceFile
+        cppCompile.setPreCompiledHeader pch
         cppCompile.execute()
 
         then:
@@ -54,12 +58,19 @@ class CppCompileTest extends Specification {
         platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
         1 * toolChain.select(platform) >> platformToolChain
         1 * platformToolChain.newCompiler({ CppCompileSpec.class.isAssignableFrom(it) }) >> cppCompiler
+        1 * pch.includeString >> "header"
+        2 * pch.prefixHeaderFile >> testDir.file("prefixHeader").createFile()
+        1 * pch.objectFile >> testDir.file("pchObjectFile").createFile()
+        2 * pch.pchObjects >> new SimpleFileCollection()
         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"
+            assert spec.preCompiledHeader == "header"
+            assert spec.prefixHeaderFile.name == "prefixHeader"
+            assert spec.preCompiledHeaderObjectFile.name == "pchObjectFile"
             true
         }) >> result
         1 * result.didWork >> true
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppPreCompiledHeaderCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppPreCompiledHeaderCompileTest.groovy
new file mode 100644
index 0000000..09dc444
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/cpp/tasks/CppPreCompiledHeaderCompileTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class CppPreCompiledHeaderCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    CppPreCompiledHeaderCompile cppPCHCompile = TestUtil.createTask(CppPreCompiledHeaderCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<CppPCHCompileSpec> cppPCHCompiler = Mock(Compiler)
+
+    def "executes using the Cpp PCH Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        cppPCHCompile.toolChain = toolChain
+        cppPCHCompile.targetPlatform = platform
+        cppPCHCompile.compilerArgs = ["arg"]
+        cppPCHCompile.macros = [def: "value"]
+        cppPCHCompile.objectFileDir = testDir.file("outputFile")
+        cppPCHCompile.source sourceFile
+        cppPCHCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "cpp"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({CppPCHCompileSpec.class.isAssignableFrom(it)}) >> cppPCHCompiler
+        1 * cppPCHCompiler.execute({ CppPCHCompileSpec 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:
+        cppPCHCompile.didWork
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializerTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializerTest.groovy
index 62cac46..bf27f17 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializerTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/CompilationStateSerializerTest.groovy
@@ -17,6 +17,8 @@
 package org.gradle.language.nativeplatform.internal.incremental
 
 import org.gradle.internal.serialize.SerializerSpec
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultInclude
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultSourceIncludes
 
 class CompilationStateSerializerTest extends SerializerSpec {
     def state = new CompilationState()
@@ -68,15 +70,15 @@ class CompilationStateSerializerTest extends SerializerSpec {
 
         def otherCompileState = newState.getState(fileTwo)
         new String(otherCompileState.hash) == "FooBar"
-        otherCompileState.sourceIncludes.systemIncludes == ["system"]
-        otherCompileState.sourceIncludes.quotedIncludes == ["quoted"]
-        otherCompileState.sourceIncludes.macroIncludes == ["MACRO"]
+        otherCompileState.sourceIncludes.systemIncludes.collect { it.value } == ["system"]
+        otherCompileState.sourceIncludes.quotedIncludes.collect { it.value } == ["quoted"]
+        otherCompileState.sourceIncludes.macroIncludes.collect { it.value } == ["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.addAll(strings.collect { DefaultInclude.parse(it, false) })
         sourceIncludes
     }
 
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParserTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParserTest.groovy
index b8e3950..0ce8875 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParserTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesParserTest.groovy
@@ -16,53 +16,55 @@
 
 package org.gradle.language.nativeplatform.internal.incremental
 
+import org.gradle.language.nativeplatform.internal.Include
+import org.gradle.language.nativeplatform.internal.SourceIncludes
 import org.gradle.language.nativeplatform.internal.incremental.sourceparser.CSourceParser
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultInclude
 import spock.lang.Specification
 
 class DefaultSourceIncludesParserTest extends Specification {
     def sourceParser = Mock(CSourceParser)
-    def sourceDetails = Mock(CSourceParser.SourceDetails)
+    def sourceIncludes = Mock(SourceIncludes)
 
-    def "imports are not included in includes"() {
+    def "returns a filtered SourceIncludes when not importAware"() {
         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._
+        1 * sourceParser.parseSource(file) >> sourceIncludes
+        1 * sourceIncludes.includesOnly >> ['"quoted"', '<system>', 'DEFINED'].collect { include(it) }
+        0 * sourceIncludes._
 
         and:
         def includes = includesParser.parseIncludes(file)
 
         then:
-        includes.quotedIncludes == ["quoted"]
-        includes.systemIncludes == ["system"]
-        includes.macroIncludes == ["DEFINED"]
+        includes.quotedIncludes.collect { it.value } == ["quoted"]
+        includes.systemIncludes.collect { it.value } == ["system"]
+        includes.macroIncludes.collect { it.value } == ["DEFINED"]
     }
 
 
-    def "imports are included in includes"() {
+    def "returns the parsed SourceIncludes when importAware"() {
         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._
+        1 * sourceParser.parseSource(file) >> sourceIncludes
+        0 * sourceIncludes._
 
         and:
         def includes = includesParser.parseIncludes(file)
 
         then:
-        includes.quotedIncludes == ["quoted", "quotedImport"]
-        includes.systemIncludes == ["system", "systemImport"]
-        includes.macroIncludes == ["DEFINED", "DEFINED_IMPORT"]
+        includes == sourceIncludes
     }
 
+    Include include(String value, boolean isImport = false) {
+        return DefaultInclude.parse(value, isImport)
+    }
 }
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolverTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolverTest.groovy
index 1355350..e4af2c0 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolverTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/DefaultSourceIncludesResolverTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.language.nativeplatform.internal.incremental
 
 import org.gradle.language.nativeplatform.internal.SourceIncludes
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultInclude
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
@@ -36,9 +37,9 @@ class DefaultSourceIncludesResolverTest extends Specification {
     def setup() {
         includes = Mock(SourceIncludes)
         includesParser.parseIncludes(sourceFile) >> includes
-        includes.getQuotedIncludes() >> quotedIncludes
-        includes.getSystemIncludes() >> systemIncludes
-        includes.getMacroIncludes() >> macroIncludes
+        includes.getQuotedIncludes() >> { quotedIncludes.collect { include(it) } }
+        includes.getSystemIncludes() >> { systemIncludes.collect { include(it) } }
+        includes.getMacroIncludes() >> { macroIncludes.collect { include(it) } }
     }
 
     protected TestFile getSourceFile() {
@@ -145,6 +146,9 @@ class DefaultSourceIncludesResolverTest extends Specification {
         }
     }
 
+    def include(String value) {
+        return DefaultInclude.parse(value, false)
+    }
     def deps(File... files) {
         return files.collect {dep(it)}
     }
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessorTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessorTest.groovy
index db8ed5a..0aa16e8 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessorTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/IncrementalCompileProcessorTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.internal.changedetection.state.FileSnapshotter
 import org.gradle.cache.PersistentStateCache
 import org.gradle.internal.hash.HashUtil
 import org.gradle.language.nativeplatform.internal.SourceIncludes
+import org.gradle.language.nativeplatform.internal.incremental.sourceparser.DefaultSourceIncludes
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultIncludeTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultIncludeTest.groovy
new file mode 100644
index 0000000..4a00aa6
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultIncludeTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal.incremental.sourceparser
+
+import org.gradle.language.nativeplatform.internal.Include
+import org.gradle.language.nativeplatform.internal.IncludeType
+import spock.lang.Specification
+
+
+class DefaultIncludeTest extends Specification {
+    def "can parse include string" () {
+        when:
+        Include include = DefaultInclude.parse(value, isImport)
+
+        then:
+        include.getValue() == includeValue
+        include.getType() == type
+        include.isImport() == isImport
+
+        where:
+        value      | includeValue | type               | isImport
+        '"quoted"' | "quoted"     | IncludeType.QUOTED | true
+        '"quoted"' | "quoted"     | IncludeType.QUOTED | false
+        "<system>" | "system"     | IncludeType.SYSTEM | true
+        "<system>" | "system"     | IncludeType.SYSTEM | false
+        "macro"    | "macro"      | IncludeType.MACRO  | true
+        "macro"    | "macro"      | IncludeType.MACRO  | false
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludesTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludesTest.groovy
new file mode 100644
index 0000000..139dae4
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/DefaultSourceIncludesTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal.incremental.sourceparser
+
+import org.gradle.language.nativeplatform.internal.Include
+import spock.lang.Specification
+
+
+class DefaultSourceIncludesTest extends Specification {
+    List<Include> includes = [ '"quoted1"', "<system1>", '"quoted2"', "macro1", "<system2>", "macro2" ].collect { DefaultInclude.parse(it, false) }
+    DefaultSourceIncludes sourceIncludes = new DefaultSourceIncludes()
+
+    def "can filter includes" () {
+        given:
+        sourceIncludes.addAll(includes)
+
+        expect:
+        sourceIncludes.quotedIncludes.collect { it.value } == [ "quoted1", "quoted2" ]
+        sourceIncludes.systemIncludes.collect { it.value } == [ "system1", "system2" ]
+        sourceIncludes.macroIncludes.collect { it.value } == [ "macro1", "macro2" ]
+    }
+
+    def "order is preserved" () {
+        given:
+        sourceIncludes.addAll(includes)
+
+        expect:
+        sourceIncludes.includesAndImports.collect { it.value } == [ "quoted1", "system1", "quoted2", "macro1", "system2", "macro2" ]
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy
index 405c405..1edbf85 100644
--- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/incremental/sourceparser/RegexBackedCSourceParserTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.language.nativeplatform.internal.incremental.sourceparser
 
+import org.gradle.language.nativeplatform.internal.Include
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
@@ -38,15 +39,15 @@ class RegexBackedCSourceParserTest extends Specification {
     }
 
     def getIncludes() {
-        return parsedSource.includes
+        return parsedSource.includesOnly
     }
 
     def getImports() {
-        return parsedSource.imports
+        return parsedSource.includesAndImports - parsedSource.includesOnly
     }
 
     def getFound() {
-        return includes + imports
+        return parsedSource.includesAndImports.collect { it.value }
     }
     
     def noIncludes() {
@@ -63,6 +64,10 @@ class RegexBackedCSourceParserTest extends Specification {
         sourceFile.text = sourceFile.text.replace("include", directive)
     }
 
+    Include include(String value, boolean isImport = false) {
+        return DefaultInclude.parse(value, isImport)
+    }
+
     def "parses file with no includes"() {
         when:
         sourceFile << ""
@@ -79,7 +84,7 @@ class RegexBackedCSourceParserTest extends Specification {
 """
 
         then:
-        includes == ['"test.h"']
+        includes == ['"test.h"'].collect { include(it) }
 
         and:
         noImports()
@@ -90,7 +95,7 @@ class RegexBackedCSourceParserTest extends Specification {
         sourceFile << '#include "test.h"'
 
         then:
-        includes == ['"test.h"']
+        includes == ['"test.h"'].collect { include(it) }
 
         and:
         noImports()
@@ -103,7 +108,7 @@ class RegexBackedCSourceParserTest extends Specification {
 """
 
         then:
-        includes == ['<test.h>']
+        includes == ['<test.h>'].collect { include(it) }
         
         and:
         noImports()
@@ -114,7 +119,7 @@ class RegexBackedCSourceParserTest extends Specification {
         sourceFile << '#include <test.h>'
 
         then:
-        includes == ['<test.h>']
+        includes == ['<test.h>'].collect { include(it) }
 
         and:
         noImports()
@@ -127,7 +132,7 @@ class RegexBackedCSourceParserTest extends Specification {
 """
 
         then:
-        includes == ['DEFINED']
+        includes == ['DEFINED'].collect { include(it) }
 
         and:
         noImports()
@@ -143,7 +148,7 @@ class RegexBackedCSourceParserTest extends Specification {
     #include DEFINED
 """
         then:
-        includes == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED']
+        includes == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED'].collect { include(it) }
         
         and:
         noImports()
@@ -156,7 +161,7 @@ class RegexBackedCSourceParserTest extends Specification {
         """
 
         then:
-        imports == ['"test.h"']
+        imports == ['"test.h"'].collect { include(it, true) }
 
         and:
         noIncludes()
@@ -169,7 +174,7 @@ class RegexBackedCSourceParserTest extends Specification {
         """
 
         then:
-        imports == ['<test.h>']
+        imports == ['<test.h>'].collect { include(it, true) }
         
         and:
         noIncludes()
@@ -182,7 +187,7 @@ class RegexBackedCSourceParserTest extends Specification {
         """
 
         then:
-        imports == ['DEFINED']
+        imports == ['DEFINED'].collect { include(it, true) }
 
         and:
         noIncludes()
@@ -198,7 +203,7 @@ class RegexBackedCSourceParserTest extends Specification {
     #import DEFINED
 """
         then:
-        imports == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED']
+        imports == ['"test1"', '"test2"', '<system1>', '<system2>', 'DEFINED'].collect { include(it, true) }
 
         and:
         noIncludes()
@@ -219,8 +224,26 @@ class RegexBackedCSourceParserTest extends Specification {
     #import DEFINED2
 """
         then:
-        includes == ['"test2"', '"test4"', '<system3>', 'DEFINED1']
-        imports == ['"test1"', '"test3"', '<system1>', '<system2>', '<system4>', 'DEFINED2']
+        includes == ['"test2"', '"test4"', '<system3>', 'DEFINED1'].collect { include(it) }
+        imports == ['"test1"', '"test3"', '<system1>', '<system2>', '<system4>', 'DEFINED2'].collect { include(it, true) }
+    }
+
+    def "preserves order of all includes and imports"() {
+        when:
+        sourceFile << """
+    #import "test1"
+    #include "test2"
+    #import "test3"
+    #include "test4"
+    #import <system1>
+    #import <system2>
+    #include <system3>
+    #import <system4>
+    #include DEFINED1
+    #import DEFINED2
+"""
+        then:
+        found == ['test1', 'test2', 'test3', 'test4', 'system1', 'system2', 'system3', 'system4', 'DEFINED1', 'DEFINED2']
     }
 
     @Unroll
@@ -241,8 +264,8 @@ class RegexBackedCSourceParserTest extends Specification {
         useDirective(directive)
 
         then:
-        found == ['"test1"', '"test2"', '"test3"', '"test4"',
-                  '<system1>', '<system2>', '<system3>', '<system4>']
+        found == ['test1', 'test2', 'test3', 'test4',
+                  'system1', 'system2', 'system3', 'system4']
 
         where:
         directive << ["include", "import"]
@@ -264,7 +287,7 @@ class RegexBackedCSourceParserTest extends Specification {
         useDirective(directive)
 
         then:
-        found == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', '<system3>']
+        found == ['test1', 'test2', 'test3', 'system1', 'system2', 'system3']
 
         where:
         directive << ["include", "import"]
@@ -285,7 +308,7 @@ class RegexBackedCSourceParserTest extends Specification {
 */
 """
         then:
-        includes == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', '<system3>']
+        includes == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', '<system3>'].collect { include(it) }
     }
 
     @Unroll
@@ -304,7 +327,7 @@ class RegexBackedCSourceParserTest extends Specification {
         useDirective(directive)
 
         then:
-        found == ['"test1"', '"test2"', '"test3"', '<system1>', '<system2>', 'DEFINED']
+        found == ['test1', 'test2', 'test3', 'system1', 'system2', 'DEFINED']
 
         where:
         directive << ["include", "import"]
@@ -317,8 +340,8 @@ class RegexBackedCSourceParserTest extends Specification {
     #import "$included"
 """
         then:
-        includes == ['"' + included + '"']
-        imports == ['"' + included + '"']
+        includes == ['"' + included + '"'].collect { include(it) }
+        imports == ['"' + included + '"'].collect { include(it, true) }
 
         where:
         included << ["test'file", "testfile'", "'testfile'", "test<>file", "test>file", "<testFile>", "test<file", "test file"]
@@ -331,8 +354,8 @@ class RegexBackedCSourceParserTest extends Specification {
     #import <$included>
 """
         then:
-        includes == ['<' + included + '>']
-        imports == ['<' + included + '>']
+        includes == ['<' + included + '>'].collect { include(it) }
+        imports == ['<' + included + '>'].collect { include(it, true) }
 
         where:
         included << ["test'file", "testfile'", "'testfile'", "test<file", "test\"file", "\"testFile\"", "test file"]
@@ -345,8 +368,8 @@ class RegexBackedCSourceParserTest extends Specification {
     #import $included
 """
         then:
-        includes == [included]
-        imports == [included]
+        includes == [included].collect { include(it) }
+        imports == [included].collect { include(it, true) }
 
         where:
         included << ["DEFINED", "mixedDefined", "DEF_INED", "_DEFINED", "__DEFINED__"]
@@ -441,6 +464,6 @@ st3"
 """
 
         then:
-        includes == ['"test1"', '"test2"', '"test3"']
+        includes == ['"test1"', '"test2"', '"test3"'].collect { include(it) }
     }
 }
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCCompileTest.groovy
new file mode 100644
index 0000000..58779a8
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCCompileTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class ObjectiveCCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    ObjectiveCCompile objCCompile = TestUtil.createTask(ObjectiveCCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<ObjectiveCCompileSpec> objCCompiler = Mock(Compiler)
+    def pch = Mock(PreCompiledHeader)
+
+    def "executes using the C Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        objCCompile.toolChain = toolChain
+        objCCompile.targetPlatform = platform
+        objCCompile.compilerArgs = ["arg"]
+        objCCompile.macros = [def: "value"]
+        objCCompile.objectFileDir = testDir.file("outputFile")
+        objCCompile.source sourceFile
+        objCCompile.setPreCompiledHeader pch
+        objCCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "objc"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({ ObjectiveCCompileSpec.class.isAssignableFrom(it) }) >> objCCompiler
+        1 * pch.includeString >> "header"
+        2 * pch.prefixHeaderFile >> testDir.file("prefixHeader").createFile()
+        1 * pch.objectFile >> testDir.file("pchObjectFile").createFile()
+        2 * pch.pchObjects >> new SimpleFileCollection()
+        1 * objCCompiler.execute({ ObjectiveCCompileSpec spec ->
+            assert spec.sourceFiles*.name == ["sourceFile"]
+            assert spec.args == ['arg']
+            assert spec.allArgs == ['arg']
+            assert spec.macros == [def: 'value']
+            assert spec.objectFileDir.name == "outputFile"
+            assert spec.preCompiledHeader == "header"
+            assert spec.prefixHeaderFile.name == "prefixHeader"
+            assert spec.preCompiledHeaderObjectFile.name == "pchObjectFile"
+            true
+        }) >> result
+        1 * result.didWork >> true
+        0 * _._
+
+        and:
+        objCCompile.didWork
+    }
+}
\ No newline at end of file
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCPreCompiledHeaderCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCPreCompiledHeaderCompileTest.groovy
new file mode 100644
index 0000000..7b28a46
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivec/tasks/ObjectiveCPreCompiledHeaderCompileTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCPCHCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class ObjectiveCPreCompiledHeaderCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    ObjectiveCPreCompiledHeaderCompile objCPCHCompile = TestUtil.createTask(ObjectiveCPreCompiledHeaderCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<ObjectiveCPCHCompileSpec> objCPCHCompiler = Mock(Compiler)
+
+    def "executes using the C PCH Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        objCPCHCompile.toolChain = toolChain
+        objCPCHCompile.targetPlatform = platform
+        objCPCHCompile.compilerArgs = ["arg"]
+        objCPCHCompile.macros = [def: "value"]
+        objCPCHCompile.objectFileDir = testDir.file("outputFile")
+        objCPCHCompile.source sourceFile
+        objCPCHCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "c"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({ObjectiveCPCHCompileSpec.class.isAssignableFrom(it)}) >> objCPCHCompiler
+        1 * objCPCHCompiler.execute({ ObjectiveCPCHCompileSpec 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:
+        objCPCHCompile.didWork
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompileTest.groovy
new file mode 100644
index 0000000..10c973f
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppCompileTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCppCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class ObjectiveCppCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    ObjectiveCppCompile objCppCompile = TestUtil.createTask(ObjectiveCppCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<ObjectiveCppCompileSpec> objCppCompiler = Mock(Compiler)
+    def pch = Mock(PreCompiledHeader)
+
+    def "executes using the objCppCompiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        objCppCompile.toolChain = toolChain
+        objCppCompile.targetPlatform = platform
+        objCppCompile.compilerArgs = ["arg"]
+        objCppCompile.macros = [def: "value"]
+        objCppCompile.objectFileDir = testDir.file("outputFile")
+        objCppCompile.source sourceFile
+        objCppCompile.setPreCompiledHeader pch
+        objCppCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "objcpp"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({ ObjectiveCppCompileSpec.class.isAssignableFrom(it) }) >> objCppCompiler
+        1 * pch.includeString >> "header"
+        2 * pch.prefixHeaderFile >> testDir.file("prefixHeader").createFile()
+        1 * pch.objectFile >> testDir.file("pchObjectFile").createFile()
+        2 * pch.pchObjects >> new SimpleFileCollection()
+        1 * objCppCompiler.execute({ ObjectiveCppCompileSpec spec ->
+            assert spec.sourceFiles*.name == ["sourceFile"]
+            assert spec.args == ['arg']
+            assert spec.allArgs == ['arg']
+            assert spec.macros == [def: 'value']
+            assert spec.objectFileDir.name == "outputFile"
+            assert spec.preCompiledHeader == "header"
+            assert spec.prefixHeaderFile.name == "prefixHeader"
+            assert spec.preCompiledHeaderObjectFile.name == "pchObjectFile"
+            true
+        }) >> result
+        1 * result.didWork >> true
+        0 * _._
+
+        and:
+        objCppCompile.didWork
+    }
+}
diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppPreCompiledHeaderCompileTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppPreCompiledHeaderCompileTest.groovy
new file mode 100644
index 0000000..0a51c40
--- /dev/null
+++ b/subprojects/language-native/src/test/groovy/org/gradle/language/objectivecpp/tasks/ObjectiveCppPreCompiledHeaderCompileTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tasks
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.language.base.internal.compile.Compiler
+import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.OperatingSystemInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCppPCHCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TestUtil
+import spock.lang.Specification
+
+
+class ObjectiveCppPreCompiledHeaderCompileTest extends Specification {
+    def testDir = new TestNameTestDirectoryProvider().testDirectory
+    ObjectiveCppPreCompiledHeaderCompile objCppPCHCompile = TestUtil.createTask(ObjectiveCppPreCompiledHeaderCompile)
+    def toolChain = Mock(NativeToolChainInternal)
+    def platform = Mock(NativePlatformInternal)
+    def platformToolChain = Mock(PlatformToolProvider)
+    Compiler<ObjectiveCppPCHCompileSpec> objCppPCHCompiler = Mock(Compiler)
+
+    def "executes using the Cpp PCH Compiler"() {
+        def sourceFile = testDir.createFile("sourceFile")
+        def result = Mock(WorkResult)
+        when:
+        objCppPCHCompile.toolChain = toolChain
+        objCppPCHCompile.targetPlatform = platform
+        objCppPCHCompile.compilerArgs = ["arg"]
+        objCppPCHCompile.macros = [def: "value"]
+        objCppPCHCompile.objectFileDir = testDir.file("outputFile")
+        objCppPCHCompile.source sourceFile
+        objCppPCHCompile.execute()
+
+        then:
+        _ * toolChain.outputType >> "objcpp"
+        platform.getArchitecture() >> Mock(ArchitectureInternal) { getName() >> "arch" }
+        platform.getOperatingSystem() >> Mock(OperatingSystemInternal) { getName() >> "os" }
+        1 * toolChain.select(platform) >> platformToolChain
+        1 * platformToolChain.newCompiler({ObjectiveCppPCHCompileSpec.class.isAssignableFrom(it)}) >> objCppPCHCompiler
+        1 * objCppPCHCompiler.execute({ ObjectiveCppPCHCompileSpec 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:
+        objCppPCHCompile.didWork
+    }
+}
diff --git a/subprojects/language-scala/src/main/java/org/gradle/language/scala/ScalaPlatform.java b/subprojects/language-scala/src/main/java/org/gradle/language/scala/ScalaPlatform.java
index 6e1cf90..c8e2b54 100644
--- a/subprojects/language-scala/src/main/java/org/gradle/language/scala/ScalaPlatform.java
+++ b/subprojects/language-scala/src/main/java/org/gradle/language/scala/ScalaPlatform.java
@@ -16,11 +16,13 @@
 
 package org.gradle.language.scala;
 
+import org.gradle.api.Incubating;
 import org.gradle.platform.base.Platform;
 
 /**
  * Defines and configures a Scala Platform.
  */
+ at Incubating
 public interface ScalaPlatform extends Platform {
     String getScalaVersion();
 
diff --git a/subprojects/language-scala/src/testFixtures/groovy/org/gradle/language/scala/fixtures/TestScalaComponent.groovy b/subprojects/language-scala/src/testFixtures/groovy/org/gradle/language/scala/fixtures/TestScalaComponent.groovy
index 2d2c55b..c11a057 100644
--- a/subprojects/language-scala/src/testFixtures/groovy/org/gradle/language/scala/fixtures/TestScalaComponent.groovy
+++ b/subprojects/language-scala/src/testFixtures/groovy/org/gradle/language/scala/fixtures/TestScalaComponent.groovy
@@ -57,4 +57,9 @@ object Extra {
 """
 
     }
+
+    @Override
+    TestFile createIgnoredFileInSources(TestFile sourceDir) {
+        sourceDir.createFile("scala/SomeIgnoredFile.scala~") << '// this file should be ignored'
+    }
 }
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index b045bac..0bd31cc 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -14,14 +14,14 @@ dependencies {
 
     testCompile libraries.groovy
 
+    integTestCompile project(':internalIntegTesting')
     integTestRuntime project(':plugins')
 
     startScriptGenerator project(':plugins')
-
-    testFixturesCompile project(':internalTesting')
 }
 
 useTestFixtures()
+useTestFixtures(project: ':languageJava')
 
 integTestTasks.all {
     if (isCiServer) {
@@ -84,4 +84,4 @@ daemonIntegTest {
     exclude "org/gradle/launcher/daemon/**/*"
 }
 
-useClassycle()
\ No newline at end of file
+useClassycle()
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 269a022..d541f00 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
@@ -21,11 +21,13 @@ import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.internal.jvm.JavaInfo
 import org.gradle.internal.jvm.Jvm
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.gradle.util.TextUtil
 import spock.lang.IgnoreIf
 
+ at LeaksFileHandles
 class GradleConfigurabilityIntegrationSpec extends AbstractIntegrationSpec {
 
     def setup() {
@@ -69,7 +71,7 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
         def javaHome = Jvm.current().javaHome
         def javaLink = file("javaLink")
         javaLink.createLink(javaHome)
-        file("tmp").deleteDir().createDir()
+        file("tmp").createDir().deleteDir()
 
         String linkPath = TextUtil.escapeString(javaLink.absolutePath)
         file("gradle.properties") << "org.gradle.java.home=$linkPath"
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/AbstractContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/AbstractContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..f27203c
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/AbstractContinuousIntegrationTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import com.google.common.util.concurrent.SimpleTimeLimiter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.*
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.process.internal.streams.SafeStreams
+import org.gradle.util.RedirectStdIn
+import org.gradle.util.TextUtil
+import org.junit.Rule
+import org.spockframework.runtime.SpockTimeoutError
+import spock.util.concurrent.PollingConditions
+
+import java.util.concurrent.TimeUnit
+
+abstract class AbstractContinuousIntegrationTest extends AbstractIntegrationSpec {
+    private static final int WAIT_FOR_WATCHING_TIMEOUT_SECONDS = 30
+    private static final int WAIT_FOR_SHUTDOWN_TIMEOUT_SECONDS = 10
+
+    GradleHandle gradle
+
+    private int standardOutputBuildMarker = 0
+    private int errorOutputBuildMarker = 0
+
+    int buildTimeout = WAIT_FOR_WATCHING_TIMEOUT_SECONDS
+    int shutdownTimeout = WAIT_FOR_SHUTDOWN_TIMEOUT_SECONDS
+    boolean expectBuildFailure = false
+    boolean killToStop
+
+    @Rule
+    RedirectStdIn redirectStdIn = new RedirectStdIn()
+    PipedOutputStream stdinPipe = redirectStdIn.getStdinPipe()
+
+    public void turnOnDebug() {
+        executer.withDebug(true)
+        executer.withArgument("--no-daemon")
+        buildTimeout *= 100
+        shutdownTimeout *= 100
+    }
+
+    public void cleanupWhileTestFilesExist() {
+        stopGradle()
+        if (OperatingSystem.current().isWindows()) {
+            // needs delay to release file handles
+            sleep(500L)
+        }
+    }
+
+    def setup() {
+        // this is here to ensure that the lastModified() timestamps actually change in between builds.
+        // if the build is very fast, the timestamp of the file will not change and the JDK file watch service won't see the change.
+        executer.beforeExecute {
+            def initScript = file("init.gradle")
+            initScript.text = """
+                def startAt = System.currentTimeMillis()
+                gradle.buildFinished {
+                    def sinceStart = System.currentTimeMillis() - startAt
+                    if (sinceStart < 2000) {
+                      sleep 2000 - sinceStart
+                    }
+                }
+            """
+            withArgument("-I").withArgument(initScript.absolutePath)
+        }
+    }
+
+    @Override
+    protected ExecutionResult succeeds(String... tasks) {
+        if (tasks) {
+            runBuild(tasks)
+        } else if (!gradle.isRunning()) {
+            throw new UnexpectedBuildFailure("Gradle has exited")
+        }
+        if (gradle == null) {
+            throw new UnexpectedBuildFailure("Gradle never started")
+        }
+        waitForBuild()
+        if (result instanceof ExecutionFailure) {
+            throw new UnexpectedBuildFailure("build was expected to succeed but failed")
+        }
+        result
+    }
+
+    ExecutionFailure fails(String... tasks) {
+        if (tasks) {
+            runBuild(tasks)
+        } else if (!gradle.isRunning()) {
+            throw new UnexpectedBuildFailure("Gradle has exited")
+        }
+        waitForBuild()
+        if (!(result instanceof ExecutionFailure)) {
+            throw new UnexpectedBuildFailure("build was expected to fail but succeeded")
+        }
+        failure = result as ExecutionFailure
+        failure
+    }
+
+    private void runBuild(String... tasks) {
+        stopGradle()
+        standardOutputBuildMarker = 0
+        errorOutputBuildMarker = 0
+
+        executer.withStdIn(System.in)
+        gradle = executer.withTasks(tasks).withForceInteractive(true).withArgument("--continuous").start()
+    }
+
+    private void waitForBuild() {
+        def lastOutput = buildOutputSoFar()
+        def lastActivity = System.currentTimeMillis()
+
+        while (gradle.isRunning() && System.currentTimeMillis() - lastActivity < (buildTimeout * 1000)) {
+            sleep 100
+            def lastLength = lastOutput.size()
+            lastOutput = buildOutputSoFar()
+
+            if (lastOutput.contains(TextUtil.toPlatformLineSeparators("Waiting for changes to input files of tasks..."))) {
+                break
+            } else if (lastOutput.size() > lastLength) {
+                lastActivity = System.currentTimeMillis()
+            }
+        }
+
+        def out = buildOutputSoFar()
+        def err = gradle.errorOutput.substring(errorOutputBuildMarker)
+        standardOutputBuildMarker = gradle.standardOutput.length()
+        errorOutputBuildMarker = gradle.errorOutput.length()
+
+        //noinspection GroovyConditionalWithIdenticalBranches
+        result = out.contains("BUILD SUCCESSFUL") ? new OutputScrapingExecutionResult(out, err) : new OutputScrapingExecutionFailure(out, err)
+    }
+
+    void stopGradle() {
+        if (gradle && gradle.isRunning()) {
+            if (killToStop) {
+                gradle.abort()
+            } else {
+                closeStdIn()
+                new SimpleTimeLimiter().callWithTimeout(
+                    { expectBuildFailure ? gradle.waitForFailure() : gradle.waitForFinish() },
+                    shutdownTimeout, TimeUnit.SECONDS, false
+                )
+            }
+        }
+    }
+
+    void closeStdIn() {
+        stdinPipe.close()
+        executer.withStdIn(SafeStreams.emptyInput())
+        redirectStdIn.resetStdinPipe()
+        stdinPipe = redirectStdIn.getStdinPipe()
+    }
+
+    void noBuildTriggered(int waitSeconds = 3) {
+        // TODO - change this general strategy to positively detect changes we are ignoring instead of asserting that a build doesn't happen in some time frame
+        try {
+            new PollingConditions(initialDelay: 0.5).within(waitSeconds) {
+                assert !buildOutputSoFar().empty
+            }
+            throw new AssertionError("Expected build not to start, but started with output: " + buildOutputSoFar())
+        } catch (SpockTimeoutError e) {
+            // ok, what we want
+        }
+    }
+
+    // should be private, but is accessed by closures in this class
+    protected String buildOutputSoFar() {
+        gradle.standardOutput.substring(standardOutputBuildMarker)
+    }
+
+    void cancelsAndExits() {
+        waitForNotRunning()
+        assert buildOutputSoFar().contains("Build cancelled.")
+    }
+
+    void doesntExit() {
+        try {
+            waitForNotRunning()
+            assert gradle.running
+        } catch (AssertionError ignore) {
+
+        }
+    }
+
+    private waitForNotRunning() {
+        new PollingConditions().within(WAIT_FOR_SHUTDOWN_TIMEOUT_SECONDS) {
+            assert !gradle.running
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/ArchivesContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/ArchivesContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..611b216
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/ArchivesContinuousIntegrationTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import spock.lang.Ignore
+import spock.lang.Unroll
+
+class ArchivesContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def "creating zips"() {
+        given:
+        def sourceDir = file("src")
+        def subDir = sourceDir.file("subdir")
+        def outputFile = file("build/distributions/zip.zip")
+        def unpackDir = file("build/unpack")
+
+        when:
+        subDir.mkdirs()
+        unpackDir.mkdirs()
+        sourceDir.file("README").text = "README"
+        subDir.file("A").text = "A"
+        buildFile << """
+            apply plugin: 'base'
+            task zip(type: Zip) {
+                archiveName = "zip.zip"
+                from("src")
+            }
+        """
+
+        then:
+        succeeds("zip")
+        executedAndNotSkipped(":zip")
+        outputFile.exists()
+        outputFile.unzipTo(unpackDir)
+        unpackDir.file("README").text == "README"
+        unpackDir.file("subdir/A").text == "A"
+
+        when:
+        subDir.file("B").text = "B"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":zip")
+        outputFile.exists()
+        outputFile.unzipTo(unpackDir)
+        unpackDir.file("subdir/B").text == "B"
+
+        when:
+        sourceDir.file("newdir").createDir()
+
+        then:
+        succeeds()
+        skipped(":zip") // GRADLE-2827 - current behaviour, not desired
+    }
+
+    @Unroll
+    def "using compressed files as inputs - #source"() {
+        given:
+        def packDir = file("pack").createDir()
+        def outputDir = file("unpack")
+        def sourceFile = file(source)
+
+        buildFile << """
+            task unpack(type: Sync) {
+                from($type("${sourceFile.toURI()}"))
+                into("unpack")
+            }
+        """
+
+        when:
+        packDir.file("A").text = "original"
+        packDir."$packType"(sourceFile)
+
+        then:
+        succeeds("unpack")
+        executedAndNotSkipped(":unpack")
+        outputDir.file("A").text == "original"
+
+        when:
+        packDir.file("A") << "-changed"
+        packDir."$packType"(sourceFile)
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":unpack")
+        outputDir.file("A").text == "original-changed"
+
+        where:
+        type      | packType | source
+        "zipTree" | "zipTo"  | "source.zip"
+        "tarTree" | "tarTo"  | "source.tar"
+        "tarTree" | "tgzTo"  | "source.tgz"
+        "tarTree" | "tbzTo"  | "source.tbz2"
+    }
+
+    @Ignore("inputs from resources are ignored")
+    def "using compressed files as inputs from resources - #source"() {
+        given:
+        def packDir = file("pack").createDir()
+        def outputDir = file("unpack")
+        def sourceFile = file(source)
+
+        buildFile << """
+            task unpack(type: Sync) {
+                from($type(resources.$resourceType("${sourceFile.toURI()}")))
+                into("unpack")
+            }
+        """
+
+        when:
+        packDir.file("A").text = "original"
+        packDir."$packType"(sourceFile)
+
+        then:
+        succeeds("unpack")
+        executedAndNotSkipped(":unpack")
+        outputDir.file("A").text == "original"
+
+        when:
+        packDir.file("A") << "-changed"
+        packDir."$packType"(sourceFile)
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":unpack")
+        outputDir.file("A").text == "original-changed"
+
+        where:
+        type      | packType | resourceType | source
+        "tarTree" | "tgzTo"  | "gzip"       | "source.tgz"
+        "tarTree" | "tbzTo"  | "bzip2"      | "source.tbz2"
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/BuildSrcContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/BuildSrcContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..a382661
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/BuildSrcContinuousIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+class BuildSrcContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def "can build and reload a project with buildSrc"() {
+        when:
+        file("buildSrc/src/main/groovy/Thing.groovy") << """
+            class Thing {
+              public static final String VALUE = "original"
+            }
+        """
+
+        buildScript """
+            task a {
+              inputs.files "a"
+              doLast {
+                println "value: " + Thing.VALUE
+              }
+            }
+        """
+
+        then:
+        succeeds("a")
+        output.contains "value: original"
+
+        when:
+        file("buildSrc/src/main/groovy/Thing.groovy").text = """
+            class Thing {
+              public static final String VALUE = "changed"
+            }
+        """
+
+        then:
+        noBuildTriggered()
+
+        when:
+        file("a") << "added"
+
+        then:
+        succeeds()
+        output.contains "value: changed"
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/CancellationContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/CancellationContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..e784b17
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/CancellationContinuousIntegrationTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import org.gradle.internal.SystemProperties
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class CancellationContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def setup() {
+        buildFile.text = "apply plugin: 'java'"
+    }
+
+    def "should cancel build when System.in contains EOT"() {
+        given:
+        succeeds("build")
+
+        when:
+        stdinPipe.write(4) // EOT / CTRL-D
+
+        // TODO: this is not right, we are sending a line ending to workaround the input buffering by the daemon
+        // Possibly, the daemon should be EOT aware and close the stream.
+        // Or, when the client is doing a blocking read of the input we shouldn't buffer.
+        stdinPipe.write(SystemProperties.instance.lineSeparator.bytes)
+
+        then:
+        cancelsAndExits()
+    }
+
+    def "should cancel build when System.in is closed"() {
+        given:
+        succeeds("build")
+
+        when:
+        closeStdIn()
+
+        then:
+        cancelsAndExits()
+    }
+
+    def "should cancel build when System.in contains some other characters, then closes"() {
+        when:
+        succeeds("build")
+        stdinPipe << 'abc'
+
+        then:
+        doesntExit()
+
+        when:
+        stdinPipe.close()
+
+        then:
+        cancelsAndExits()
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS) // GradleHandle.abort() is unsafe on Windows - this is a test infrastructure problem
+    def "does not cancel on EOT or by closing System.in when not interactive"() {
+        when:
+        executer.beforeExecute { it.withForceInteractive(false) }
+        killToStop = true
+        closeStdIn()
+
+        then:
+        succeeds "build" // tests message
+        output.endsWith("...\n")
+
+        when:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        then:
+        succeeds()
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/Java7RequiringContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/Java7RequiringContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..d36be4e
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/Java7RequiringContinuousIntegrationTest.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.JDK7_OR_LATER)
+abstract class Java7RequiringContinuousIntegrationTest extends AbstractContinuousIntegrationTest {
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/JdkVersionsContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/JdkVersionsContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..df3cba2
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/JdkVersionsContinuousIntegrationTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import org.gradle.api.JavaVersion
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.internal.jvm.JavaInfo
+import org.gradle.util.Requires
+
+ at Requires(adhoc = {
+    // not quite right, we want to allow coverage builds testing against a real distro
+    JavaVersion.current().java7Compatible || !GradleContextualExecuter.embedded
+})
+class JdkVersionsContinuousIntegrationTest extends AbstractContinuousIntegrationTest {
+
+    def setup() {
+        executer.requireGradleHome()
+    }
+
+    @Requires(adhoc = { JdkVersionsContinuousIntegrationTest.java6() })
+    def "requires java 7 build runtime"() {
+        when:
+        executer.withJavaHome(java6().javaHome)
+
+        then:
+        fails("tasks")
+
+        and:
+        failureDescriptionContains("Continuous build requires Java 7 or later.")
+    }
+
+    @Requires(adhoc = { JdkVersionsContinuousIntegrationTest.java6() && JdkVersionsContinuousIntegrationTest.java7OrBetter() })
+    def "can use java6 client with later build runtime"() {
+        given:
+        executer
+            .withJavaHome(java6().javaHome)
+            .withArgument("-Dorg.gradle.java.home=${java7OrBetter().javaHome}")
+        file("a").text = "foo"
+        buildScript """
+            task a {
+              inputs.file "a"
+              doLast {
+                println "text: " + file("a").text
+              }
+            }
+        """
+
+        when:
+        succeeds "a"
+
+        then:
+        output.contains("text: foo")
+
+        when:
+        file("a").text = "bar"
+
+        then:
+        succeeds()
+    }
+
+    static JavaInfo java6() {
+        AvailableJavaHomes.getJdk(JavaVersion.VERSION_1_6)
+    }
+
+    static JavaInfo java7OrBetter() {
+        AvailableJavaHomes.getAvailableJdk { it.javaVersion.isJava7Compatible() }
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/MultiProjectContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/MultiProjectContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..def1611
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/MultiProjectContinuousIntegrationTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+class MultiProjectContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def upstreamSource, downstreamSource
+
+    def setup() {
+        executer.noExtraLogging().withStackTraceChecksDisabled()
+        settingsFile << "include 'upstream', 'downstream'"
+        buildFile << """
+            subprojects {
+                apply plugin: 'java'
+                repositories { mavenCentral() }
+            }
+
+            project(':downstream') {
+                dependencies {
+                    compile project(":upstream")
+                }
+            }
+        """
+
+        upstreamSource = file("upstream/src/main/java/Upstream.java") << "class Upstream {}"
+        downstreamSource = file("downstream/src/main/java/Downstream.java").createFile() << "class Downstream {}"
+    }
+
+    def "changes to upstream project triggers build of downstream"() {
+        expect:
+        succeeds "build"
+        executedAndNotSkipped ":upstream:compileJava", ":downstream:compileJava"
+
+        when:
+        upstreamSource.text = "class Upstream { int change = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":upstream:compileJava", ":downstream:compileJava"
+
+        when:
+        downstreamSource.text = "class Downstream { int change = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":downstream:compileJava"
+        skipped ":upstream:compileJava"
+
+        when:
+        upstreamSource.text = "class Upstream {"
+
+        then:
+        fails()
+
+        when:
+        downstreamSource.text = "class Downstream { int change = 11; }"
+
+        then:
+        noBuildTriggered()
+
+        when:
+        downstreamSource.text = "class Downstream {}"
+        upstreamSource.text = "class Upstream {}"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":upstream:compileJava", ":downstream:compileJava"
+    }
+
+    def "can specify root directory of multi project build as a task input; changes are respected"() {
+        given:
+        buildFile << """
+            allprojects {
+                task a {
+                    inputs.dir rootDir
+                    doLast {
+                    }
+                }
+            }
+        """
+
+        expect:
+        succeeds "a"
+        executedAndNotSkipped(":a", ":upstream:a", ":downstream:a")
+
+        when:
+        file("A").text = "A"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":a", ":upstream:a", ":downstream:a")
+
+        expect:
+        succeeds(":downstream:a")
+        executedAndNotSkipped(":downstream:a")
+        notExecuted(":a", ":upstream:a")
+
+        when:
+        file("B").text = "B"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":downstream:a")
+    }
+
+    // here to put more stress on parallel execution
+    def "reasonable sized multi-project"() {
+        given:
+        def extraProjectNames = (0..100).collect { "project$it" }
+        extraProjectNames.each {
+            settingsFile << "\n include '$it' \n"
+            buildFile << "\n project(':$it') { dependencies { compile project(':upstream') } } \n"
+            file("${it}/src/main/java/${it}/Thing.java").createFile() << """
+                package ${it};
+                class Thing {}
+            """
+        }
+
+        String[] extraCompileTasks = extraProjectNames.collect { ":$it:compileJava" }*.toString().toArray()
+        def anExtraProjectName = extraProjectNames[(extraProjectNames.size() / 2).toInteger()]
+
+        expect:
+        succeeds("build")
+
+        when:
+        downstreamSource.text = "class Downstream { int change = 1; }"
+
+        then:
+        succeeds()
+        skipped extraCompileTasks
+
+        when:
+        upstreamSource.text = "class Upstream { int change = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped extraCompileTasks
+
+        when:
+        file("${anExtraProjectName}/src/main/java/Thing.java").text = "class Thing { int change = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":$anExtraProjectName:compileJava"
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SimpleJavaContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SimpleJavaContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..4e69143
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SimpleJavaContinuousIntegrationTest.groovy
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import java.util.jar.JarOutputStream
+import java.util.zip.ZipEntry
+
+// NB: there's nothing specific about Java support and continuous.
+//     this spec just lays out some more practical use cases than the other targeted tests.
+class SimpleJavaContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'java'
+        """
+        executer.withStackTraceChecksDisabled() // some tests fail compilation
+    }
+
+    def "can build when no source dir present"() {
+        when:
+        assert !file("src/main/java").exists()
+
+        then:
+        succeeds("build")
+        ":compileJava" in skippedTasks
+        ":build" in executedTasks
+    }
+
+    def "can build when source dir is removed"() {
+        when:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        then:
+        succeeds("build")
+        executedAndNotSkipped ":compileJava", ":build"
+
+        when:
+        assert !file("src/main/java").deleteDir().exists()
+
+        then:
+        succeeds()
+        ":compileJava" in skippedTasks
+        ":build" in executedTasks
+    }
+
+    def "build is not triggered when a new directory is created in the source inputs"() {
+        when:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        then:
+        succeeds("build")
+
+        when:
+        file("src/main/java/foo").createDir()
+
+        then:
+        noBuildTriggered()
+    }
+
+    def "after compilation failure, fixing file retriggers build"() {
+        when:
+        def sourceFile = file("src/main/java/Thing.java") << "class Thing {} /* broken compile "
+
+        then:
+        fails("build")
+
+        when:
+        sourceFile << "*/"
+
+        then:
+        succeeds()
+    }
+
+    def "can run tests"() {
+        when:
+        def sourceFile = file("src/main/java/Thing.java") << "class Thing {}"
+        def testFile = file("src/test/java/TestThing.java") << "class TestThing {}"
+        def resourceFile = file("src/main/resources/thing.text") << "thing"
+
+        then:
+        succeeds("test")
+        executedAndNotSkipped(":compileJava", ":processResources", ":compileTestJava", ":test")
+
+        when:
+        sourceFile.text = "class Thing { static public int FLAG = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":compileJava", ":compileTestJava", ":test")
+        skipped(":processResources")
+
+        when:
+        testFile.text = "class TestThing { static public int FLAG = 1; }"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":compileTestJava", ":test")
+        skipped(":processResources", ":compileJava")
+
+        when:
+        resourceFile << "# another change"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":processResources", ":compileTestJava", ":test")
+        skipped(":compileJava")
+    }
+
+    def "failing main source build ignores changes to test source"() {
+        when:
+        def sourceFile = file("src/main/java/Thing.java") << "class Thing {}"
+        def testSourceFile = file("src/test/java/ThingTest.java") << "class ThingTest {}"
+
+        then:
+        succeeds("test")
+
+        when:
+        testSourceFile << " broken "
+
+        then:
+        fails()
+        failureDescriptionStartsWith("Execution failed for task ':compileTestJava'.")
+
+        when:
+        sourceFile << " broken "
+
+        then:
+        fails()
+        failureDescriptionStartsWith("Execution failed for task ':compileJava'.")
+
+        when:
+        testSourceFile.text = "class ThingTest {}"
+
+        then:
+        noBuildTriggered()
+
+        when:
+        sourceFile.text = "class Thing {}"
+
+        then:
+        succeeds()
+    }
+
+    // Just exercises the dependency management layers to shake out any weirdness
+    def "can resolve dependencies from remote repository"() {
+        when:
+        def sourceFile = file("src/main/java/Thing.java") << "class Thing {}"
+
+        buildFile << """
+            repositories {
+                mavenCentral()
+            }
+            dependencies {
+                compile "log4j:log4j:1.2.17"
+            }
+        """
+
+        then:
+        succeeds("build")
+        executedAndNotSkipped ":compileJava"
+
+        when:
+        sourceFile.text = "import org.apache.log4j.LogManager; class Thing {}"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+    }
+
+    def "dependencies as inputs from local filesystem"() {
+        when:
+        def somelib = file("lib/somelib.jar")
+        somelib.parentFile.mkdir()
+        file("src/main/java/Thing.java") << "class Thing {}"
+        buildFile << """
+            dependencies {
+                compile files("lib/somelib.jar")
+            }
+        """
+
+        then:
+        succeeds("build")
+        executedAndNotSkipped ":compileJava", ":build"
+
+        when:
+        createJar(somelib, "META-INF/")
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+
+        when:
+        createJar(somelib, "META-INF/", "another-dir/")
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+
+        when:
+        somelib.delete()
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+    }
+
+    def "multiple dependencies as inputs from local filesystem"() {
+        when:
+        def libDir = file('libs').createDir()
+        createJar(libDir.file("somelib.jar"), "META-INF/")
+        file("src/main/java/Thing.java") << "class Thing {}"
+        buildFile << """
+            dependencies {
+                compile fileTree("libs/")
+            }
+        """
+
+        then:
+        succeeds("build")
+        executedAndNotSkipped ":compileJava", ":build"
+
+        when:
+        createJar(libDir.file("anotherlib.jar"), "META-INF/")
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+    }
+
+    def "creation of initial source file triggers build"() {
+        expect:
+        succeeds("build")
+        ":compileJava" in skippedTasks
+        ":build" in executedTasks
+
+        when:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        then:
+        succeeds()
+        executedAndNotSkipped ":compileJava"
+    }
+
+    static void createJar(File jarFile, String... entries) throws IOException {
+        def jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile))
+        jarOutputStream.withCloseable {
+            entries.each {
+                jarOutputStream.putNextEntry(new ZipEntry(it))
+            }
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SmokeContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SmokeContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..6da52c0
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/SmokeContinuousIntegrationTest.groovy
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous
+
+import org.gradle.internal.environment.GradleBuildEnvironment
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class SmokeContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+
+    def "basic smoke test"() {
+        when:
+        file("marker").text = "original"
+
+        buildFile << """
+            task echo {
+              inputs.files file("marker")
+              doLast {
+                println "value: " + file("marker").text
+              }
+            }
+        """
+
+        then:
+        succeeds("echo")
+        output.contains "Continuous build is an incubating feature."
+        output.contains "value: original"
+
+        when:
+        file("marker").text = "changed"
+
+        then:
+        succeeds()
+        output.contains "value: changed"
+    }
+
+    def "can recover from build failure"() {
+        when:
+        executer.withStackTraceChecksDisabled()
+        buildFile << """
+            task build {
+              def f = file("marker")
+              inputs.files f
+              doLast {
+                if (f.file) {
+                  println "value: " + f.text
+                } else {
+                  throw new Exception("file does not exist")
+                }
+              }
+            }
+        """
+        def markerFile = file("marker") << "original"
+
+        then:
+        succeeds "build"
+        output.contains "value: original"
+
+        when:
+        markerFile.delete()
+
+        then:
+        fails()
+        errorOutput.contains "file does not exist"
+
+        when:
+        markerFile << "changed"
+
+        then:
+        succeeds()
+        output.contains "value: changed"
+    }
+
+    def "does not trigger when changes is made to task that is not required"() {
+        when:
+        buildFile << """
+            task a {
+              inputs.file "a"
+              doLast {}
+            }
+            task b {
+              inputs.file "b"
+              doLast {}
+            }
+        """
+
+        then:
+        succeeds("a")
+        ":a" in executedTasks
+
+        when:
+        file("a") << "original"
+
+        then:
+        succeeds()
+        ":a" in executedTasks
+
+        and:
+        succeeds("b")
+        ":b" in executedTasks
+
+        when:
+        file("a").text = "changed"
+
+        then:
+        noBuildTriggered()
+    }
+
+    def "exits when build fails with compile error"() {
+        when:
+        buildFile << """
+            'script error
+        """
+
+        then:
+        fails("a")
+        !gradle.running
+        output.contains("Exiting continuous build as no executed tasks declared file system inputs.")
+    }
+
+    def "exits when build fails with configuration error"() {
+        when:
+        buildFile << """
+            throw new Exception("!")
+        """
+
+        then:
+        fails("a")
+        !gradle.running
+        output.contains("Exiting continuous build as no executed tasks declared file system inputs.")
+    }
+
+    def "exits when no executed tasks have file system inputs"() {
+        when:
+        buildFile << """
+            task a
+        """
+
+        then:
+        succeeds("a")
+        !gradle.running
+        output.contains("Exiting continuous build as no executed tasks declared file system inputs.")
+    }
+
+    def "reuses build script classes"() {
+        when:
+        file("marker").text = "original"
+
+        buildFile << """
+            task echo {
+              inputs.files file("marker")
+              doLast {
+                println "value: " + file("marker").text
+                println "reuse: " + Reuse.initialized
+                Reuse.initialized = true
+              }
+            }
+            class Reuse {
+                public static Boolean initialized = false
+            }
+        """
+
+        then:
+        succeeds("echo")
+        output.contains "value: original"
+        output.contains "reuse: false"
+
+        when:
+        file("marker").text = "changed"
+
+        then:
+        succeeds()
+        output.contains "value: changed"
+        output.contains "reuse: true"
+
+    }
+
+    def "considered to be long lived process"() {
+        when:
+        buildFile << """
+            task echo {
+              doLast {
+                println "isLongLivingProcess: " + services.get($GradleBuildEnvironment.name).isLongLivingProcess()
+              }
+            }
+        """
+
+        then:
+        succeeds("echo")
+        output.contains "isLongLivingProcess: true"
+    }
+
+    def "failure to determine inputs has a reasonable message"() {
+        when:
+        buildScript """
+            task a {
+                inputs.files files({ throw new Exception("boom") })
+                doLast {}
+            }
+        """
+
+        then:
+        fails("a")
+        failureDescriptionContains("Could not determine the dependencies of task ':a'.")
+    }
+
+    def "ignores non source when source is empty"() {
+        when:
+        buildScript """
+            task build {
+              inputs.source fileTree("source")
+              inputs.files fileTree("ancillary")
+              doLast {}
+            }
+        """
+
+        then:
+        succeeds("build")
+
+        when:
+        file("ancillary/test.txt") << "foo"
+
+        then:
+        noBuildTriggered()
+
+        when:
+        file("source/test.txt") << "foo"
+
+        then:
+        succeeds()
+
+        when:
+        file("ancillary/test.txt") << "-bar"
+
+        then:
+        succeeds()
+    }
+
+    def "project directory can be used as input"() {
+        given:
+        buildFile << """
+        task a {
+            inputs.dir projectDir
+            doLast {}
+        }
+        """
+
+        expect:
+        succeeds("a")
+        executedAndNotSkipped(":a")
+
+        when:
+        file("A").text = "A"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":a")
+
+        when: "file is changed"
+        file("A").text = "B"
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":a")
+
+        when:
+        file("A").delete()
+
+        then:
+        succeeds()
+        executedAndNotSkipped(":a")
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "exit hint does not mention enter when not on windows"() {
+        when:
+        buildScript "task a { inputs.file 'a'; doLast {} }"
+
+        then:
+        succeeds "a"
+        output.endsWith("(ctrl-d to exit)\n")
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "exit hint mentions enter when on windows"() {
+        when:
+        buildScript "task a { inputs.file 'a'; doLast {} }"
+
+        then:
+        succeeds "a"
+        output.endsWith("(ctrl-d then enter to exit)\r\n")
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/jdk7/SymlinkContinuousIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/jdk7/SymlinkContinuousIntegrationTest.groovy
new file mode 100644
index 0000000..66248b6
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/jdk7/SymlinkContinuousIntegrationTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.continuous.jdk7
+
+import org.gradle.launcher.continuous.Java7RequiringContinuousIntegrationTest
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import java.nio.file.Files
+import java.nio.file.Paths
+
+ at Requires(TestPrecondition.NOT_WINDOWS)
+class SymlinkContinuousIntegrationTest extends Java7RequiringContinuousIntegrationTest {
+    def "can use symlink for input"() {
+        given:
+        def baseDir = file("src").createDir()
+        def sourceFile = baseDir.file("A")
+        sourceFile.text = "original"
+        def symlink = baseDir.file("link")
+        buildFile << """
+    task echo {
+        def symlink = file("${symlink.toURI()}")
+        inputs.files symlink
+        doLast {
+            println "text: " + (symlink.exists() ? symlink.text:"missing")
+        }
+    }
+"""
+        when: "symlink is used as input and exists"
+        Files.createSymbolicLink(Paths.get(symlink.toURI()), Paths.get(sourceFile.toURI()))
+        then:
+        succeeds("echo")
+        executedAndNotSkipped(":echo")
+        output.contains("text: original")
+        when: "symlink is deleted"
+        symlink.delete()
+        then:
+        succeeds()
+        executedAndNotSkipped(":echo")
+        output.contains("text: missing")
+        when: "symlink is created"
+        Files.createSymbolicLink(Paths.get(symlink.toURI()), Paths.get(sourceFile.toURI()))
+        then:
+        succeeds()
+        executedAndNotSkipped(":echo")
+        output.contains("text: original")
+        when: "changes made to target of symlink"
+        sourceFile.text = "changed"
+        then:
+        noBuildTriggered()
+    }
+
+    def "can use symlinked directory for input"() {
+        given:
+        def baseDir = file("src").createDir()
+        def targetDir = baseDir.file("target").createDir()
+        targetDir.files("A", "B")*.createFile()
+
+        def symlink = baseDir.file("link")
+        buildFile << """
+    task echo {
+        def symlink = files("${symlink.toURI()}")
+        inputs.files symlink
+        doLast {
+            println "isEmpty: " + symlink.isEmpty()
+        }
+    }
+"""
+        Files.createSymbolicLink(Paths.get(symlink.toURI()), Paths.get(targetDir.toURI()))
+        expect:
+        succeeds("echo")
+        executedAndNotSkipped(":echo")
+        output.contains("isEmpty: false")
+        when: "symlink is deleted"
+        symlink.delete()
+        then:
+        noBuildTriggered()
+// TODO: This behavior seems inconsistent with symlinked files
+//        succeeds()
+//        executedAndNotSkipped(":echo")
+//        output.contains("isEmpty: true")
+//        when: "symlink is created"
+//        Files.createSymbolicLink(Paths.get(symlink.toURI()), Paths.get(targetDir.toURI()))
+//        then:
+//        succeeds()
+//        executedAndNotSkipped(":echo")
+//        output.contains("isEmpty: false")
+        when: "changes made to target of symlink"
+        Files.createSymbolicLink(Paths.get(symlink.toURI()), Paths.get(targetDir.toURI()))
+        targetDir.file("C").createFile()
+        then:
+        noBuildTriggered()
+        when: "target directory is removed"
+        targetDir.deleteDir()
+        then:
+        noBuildTriggered()
+    }
+}
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 3803c31..9ece83a 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
@@ -16,12 +16,15 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Timeout
 
 import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
+ at LeaksFileHandles
 class DaemonFeedbackIntegrationSpec extends DaemonIntegrationSpec {
     def "daemon keeps logging to the file even if the build is stopped"() {
         given:
@@ -158,7 +161,7 @@ task sleep << {
 
         when:
         def daemon = executer.noExtraLogging().withArguments("--foreground").start()
-        
+
         then:
         poll(60) { assert daemon.standardOutput.contains(DaemonMessages.PROCESS_STARTED) }
 
@@ -184,16 +187,14 @@ task sleep << {
         debugBuild.output.count("debug me!") == 1
     }
 
-    List<File> getLogs(baseDir) {
+    List<File> getLogs(File baseDir) {
         //the gradle version dir
         def daemonDir = new File(baseDir, GradleVersion.current().version)
         assert daemonDir.exists()
-        def daemonFiles = daemonDir.listFiles()
-
-        daemonFiles.findAll { it.name.endsWith('.log') }
+        daemonDir.listFiles().findAll { it.name.endsWith('.log') }
     }
 
-    String readLog(baseDir) {
+    String readLog(File baseDir) {
         def logs = getLogs(baseDir)
 
         //assert single log
@@ -201,12 +202,12 @@ task sleep << {
 
         logs[0].text
     }
-    
-    void printAllLogs(baseDir) {
+
+    void printAllLogs(File baseDir) {
         getLogs(baseDir).each { println "\n---- ${it.name} ----\n${it.text}\n--------\n" }
     }
 
-    File firstLog(baseDir) {
+    File firstLog(File baseDir) {
         getLogs(baseDir)[0]
     }
 }
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonHealthLoggingIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonHealthLoggingIntegrationTest.groovy
index 56dfc71..39e6e16 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonHealthLoggingIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonHealthLoggingIntegrationTest.groovy
@@ -16,11 +16,9 @@
 
 package org.gradle.launcher.daemon
 
-class DaemonHealthLoggingIntegrationTest extends DaemonIntegrationSpec {
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 
-    def setup() {
-        executer.requireIsolatedDaemons()
-    }
+class DaemonHealthLoggingIntegrationTest extends DaemonIntegrationSpec {
 
     def "health information is present in build log"() {
         file("gradle.properties") << "org.gradle.jvmargs=-Dorg.gradle.daemon.performance.logging=true"
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy
index 2786117..6a772d1 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy
@@ -18,6 +18,7 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
 import org.gradle.integtests.fixtures.executer.DefaultGradleDistribution
 import org.gradle.internal.os.OperatingSystem
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 9eef334..6e6f8e2 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
@@ -17,6 +17,7 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.KillProcessAvailability
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.junit.Rule
 import org.junit.rules.ExternalResource
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
deleted file mode 100644
index bdd2b01..0000000
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.launcher.daemon
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
-import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
-import org.gradle.launcher.daemon.testing.DaemonsFixture
-
-abstract class DaemonIntegrationSpec extends AbstractIntegrationSpec {
-    String output
-
-    @Override
-    DaemonGradleExecuter getExecuter() {
-        super.executer as DaemonGradleExecuter
-    }
-
-    def setup() {
-        executer = new DaemonGradleExecuter(distribution, temporaryFolder)
-        executer.requireIsolatedDaemons()
-    }
-
-    @Override
-    protected void cleanupWhileTestFilesExist() {
-        // Need to kill daemons before test files are cleaned up, as the log files and registry are used to locate the daemons and these live under
-        // the test file directory.
-        daemons.killAll()
-    }
-
-    void stopDaemonsNow() {
-        def result = executer.withArguments("--stop", "--info").run()
-        output = result.output
-    }
-
-    void buildSucceeds(String script = '') {
-        file('build.gradle') << script
-        def result = executer.withArguments("--info").withNoDefaultJvmArgs().run()
-        output = result.output
-    }
-
-    DaemonsFixture getDaemons() {
-        new DaemonLogsAnalyzer(executer.daemonBaseDir)
-    }
-}
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 f2cd692..3cc3038 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
@@ -17,11 +17,12 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.integtests.fixtures.executer.GradleHandle
 import org.gradle.internal.jvm.Jvm
-import org.gradle.launcher.daemon.testing.DaemonContextParser
+import org.gradle.integtests.fixtures.daemon.DaemonContextParser
 import org.gradle.launcher.daemon.testing.DaemonEventSequenceBuilder
-import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
+import org.gradle.integtests.fixtures.daemon.DaemonLogsAnalyzer
 import spock.lang.IgnoreIf
 
 import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonNativeServicesIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonNativeServicesIntegrationTest.groovy
deleted file mode 100644
index 1a856c2..0000000
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonNativeServicesIntegrationTest.groovy
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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
-
-class DaemonNativeServicesIntegrationTest extends DaemonIntegrationSpec {
-    def "native services use daemon base dir" () {
-        given:
-        executer.requireOwnGradleUserHomeDir()
-        def nativeDir = new File(executer.gradleUserHomeDir, "native")
-
-        expect:
-        !nativeDir.exists()
-
-        when:
-        executer.withArguments("-q").run()
-
-        then:
-        nativeDir.directory
-    }
-}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonOutputToggleIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonOutputToggleIntegrationTest.groovy
index b8c0df8..00832b5 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonOutputToggleIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonOutputToggleIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.server.exec.LogToClient
 
 class DaemonOutputToggleIntegrationTest extends DaemonIntegrationSpec {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringIntegrationTest.groovy
index 76cecba..2682f05 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringIntegrationTest.groovy
@@ -18,14 +18,15 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.server.health.DaemonStatus
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class DaemonPerformanceMonitoringIntegrationTest extends DaemonIntegrationSpec {
 
     def setup() {
-        executer
-                .requireIsolatedDaemons()
-                .withGradleOpts("-D${DaemonStatus.EXPIRE_AT_PROPERTY}=80")
+        executer.withGradleOpts("-D${DaemonStatus.EXPIRE_AT_PROPERTY}=80")
     }
 
     def "when build leaks more than available memory the daemon is expired eagerly"() {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReuseIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReuseIntegrationTest.groovy
index 793ac5b..28a0141 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReuseIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReuseIntegrationTest.groovy
@@ -16,6 +16,8 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
+
 class DaemonReuseIntegrationTest extends DaemonIntegrationSpec {
 
     def "idle daemon is reused in preference to starting a new daemon"() {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonStartupMessageIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonStartupMessageIntegrationTest.groovy
index b8c26dd..840d2be 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonStartupMessageIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonStartupMessageIntegrationTest.groovy
@@ -16,14 +16,12 @@
 
 package org.gradle.launcher.daemon
 
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.client.DefaultDaemonConnector
-import spock.lang.IgnoreIf
 
 import static org.gradle.launcher.daemon.client.DefaultDaemonConnector.DISABLE_STARTING_DAEMON_MESSAGE_PROPERTY
 
- at IgnoreIf({ !GradleContextualExecuter.daemon })
-class DaemonStartupMessageIntegrationTest extends IsolatedDaemonSpec {
+class DaemonStartupMessageIntegrationTest extends DaemonIntegrationSpec {
 
     def setup() {
         executer.withDaemonStartingMessageEnabled()
@@ -58,4 +56,4 @@ class DaemonStartupMessageIntegrationTest extends IsolatedDaemonSpec {
         then:
         !output.contains(DefaultDaemonConnector.STARTING_DAEMON_MESSAGE)
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
index 00b642b..5800f3f 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import spock.lang.Issue
 
 @Issue("GRADLE-2460")
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 20f43a6..2efdce6 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,6 +16,8 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
+
 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/IsolatedDaemonSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/IsolatedDaemonSpec.groovy
index 1668bf7..51919de 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/IsolatedDaemonSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/IsolatedDaemonSpec.groovy
@@ -17,8 +17,8 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
-import org.gradle.launcher.daemon.testing.DaemonsFixture
+import org.gradle.integtests.fixtures.daemon.DaemonLogsAnalyzer
+import org.gradle.integtests.fixtures.daemon.DaemonsFixture
 
 abstract class IsolatedDaemonSpec extends AbstractIntegrationSpec {
 
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/LocaleSupportDaemonIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/LocaleSupportDaemonIntegrationTest.groovy
index 0abd23b..65c719a 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/LocaleSupportDaemonIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/LocaleSupportDaemonIntegrationTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.launcher.daemon
 
 import org.apache.commons.lang.LocaleUtils
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import spock.lang.Issue
 
 @Issue("https://issues.gradle.org/browse/GRADLE-3142")
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ProcessCrashHandlingIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ProcessCrashHandlingIntegrationTest.groovy
index c2e36f5..15119ca 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ProcessCrashHandlingIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/ProcessCrashHandlingIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.launcher.daemon
 
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.client.DaemonDisappearedException
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.test.fixtures.server.http.CyclicBarrierHttpServer
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 77e3d22..8a9b835 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
@@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.launcher.daemon.client.DefaultDaemonConnector
 import org.gradle.launcher.daemon.client.SingleUseDaemonClient
-import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
+import org.gradle.integtests.fixtures.daemon.DaemonLogsAnalyzer
 import org.gradle.util.GradleVersion
 import spock.lang.IgnoreIf
 
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 7262df8..018b6e2 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
@@ -17,6 +17,7 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.launcher.daemon.server.api.DaemonStoppedException
 import org.gradle.test.fixtures.server.http.CyclicBarrierHttpServer
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 018e319..7e796ed 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,22 +18,15 @@ package org.gradle.launcher.cli;
 
 import org.gradle.StartParameter;
 import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.cli.CommandLineConverter;
 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.LayoutCommandLineConverter;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.nativeintegration.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.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.ForegroundDaemonAction;
 import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.client.DaemonClientFactory;
@@ -47,91 +40,40 @@ import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.logging.internal.OutputEventListener;
 
 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 STOP = "stop";
-
+    private final CommandLineConverter<Parameters> parametersConverter;
     private final ServiceRegistry loggingServices;
-    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());
-    }
-
-    BuildActionsFactory(ServiceRegistry loggingServices, DefaultCommandLineConverter commandLineConverter,
-                        DaemonCommandLineConverter daemonConverter, LayoutCommandLineConverter layoutConverter,
-                        SystemPropertiesCommandLineConverter propertiesConverter,
-                        LayoutToPropertiesConverter layoutToPropertiesConverter,
-                        PropertiesToStartParameterConverter propertiesToStartParameterConverter,
-                        PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter) {
+    BuildActionsFactory(ServiceRegistry loggingServices, CommandLineConverter<Parameters> parametersConverter) {
         this.loggingServices = loggingServices;
-        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());
+        this.parametersConverter = parametersConverter;
     }
 
     public void configureCommandLineParser(CommandLineParser parser) {
-        commandLineConverter.configure(parser);
-        daemonConverter.configure(parser);
-
-        parser.option(FOREGROUND).hasDescription("Starts the Gradle daemon in the foreground.").incubating();
-        parser.option(STOP).hasDescription("Stops the Gradle daemon if it is running.");
+        parametersConverter.configure(parser);
     }
 
     public Runnable 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();
-        propertiesToStartParameterConverter.convert(properties, startParameter);
-        commandLineConverter.convert(commandLine, startParameter);
-
-        DaemonParameters daemonParameters = new DaemonParameters(layout, startParameter.getSystemPropertiesArgs());
-        propertiesToDaemonParametersConverter.convert(properties, daemonParameters);
-        daemonConverter.convert(commandLine, daemonParameters);
+        Parameters parameters = parametersConverter.convert(commandLine, new Parameters());
 
-        if (commandLine.hasOption(STOP)) {
-            return stopAllDaemons(daemonParameters, loggingServices);
+        if (parameters.getDaemonParameters().isStop()) {
+            return stopAllDaemons(parameters.getDaemonParameters(), loggingServices);
         }
-        if (commandLine.hasOption(FOREGROUND)) {
+        if (parameters.getDaemonParameters().isForeground()) {
+            DaemonParameters daemonParameters = parameters.getDaemonParameters();
             ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration(
                     daemonParameters.getUid(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout());
             return new ForegroundDaemonAction(loggingServices, conf);
         }
-        if (daemonParameters.getDaemonUsage().isEnabled()) {
-            return runBuildWithDaemon(startParameter, daemonParameters, loggingServices);
+        if (parameters.getDaemonParameters().getDaemonUsage().isEnabled()) {
+            return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
         }
-        if (canUseCurrentProcess(daemonParameters)) {
-            return runBuildInProcess(startParameter, daemonParameters, loggingServices);
+        if (canUseCurrentProcess(parameters.getDaemonParameters())) {
+            return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
         }
-        return runBuildInSingleUseDaemon(startParameter, daemonParameters, loggingServices);
+
+        return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
     }
 
     private Runnable stopAllDaemons(DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
@@ -159,12 +101,14 @@ class BuildActionsFactory implements CommandLineAction {
                 .displayName("Global services")
                 .parent(loggingServices)
                 .parent(NativeServices.getInstance())
-                .provider(new GlobalScopeServices(false))
+                .provider(new GlobalScopeServices(startParameter.isContinuous()))
                 .build();
-        InProcessBuildActionExecuter executer = globalServices.get(InProcessBuildActionExecuter.class);
+
+        BuildActionExecuter<BuildActionParameters> executer = globalServices.get(BuildExecuter.class);
         StyledTextOutputFactory textOutputFactory = globalServices.get(StyledTextOutputFactory.class);
         DocumentationRegistry documentationRegistry = globalServices.get(DocumentationRegistry.class);
         DaemonUsageSuggestingBuildActionExecuter daemonUsageSuggestingExecuter = new DaemonUsageSuggestingBuildActionExecuter(executer, textOutputFactory, documentationRegistry);
+
         return runBuild(startParameter, daemonParameters, daemonUsageSuggestingExecuter);
     }
 
@@ -200,7 +144,7 @@ class BuildActionsFactory implements CommandLineAction {
                 System.getenv(),
                 SystemProperties.getInstance().getCurrentDir(),
                 startParameter.getLogLevel(),
-                daemonParameters.getDaemonUsage());
+                daemonParameters.getDaemonUsage(), startParameter.isContinuous(), daemonParameters.isInteractive());
         return new RunBuildAction(executer, startParameter, clientMetaData(), getBuildStartTime(), parameters);
     }
 
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 53999da..4a6a7b8 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
@@ -72,7 +72,7 @@ public class CommandLineActionFactory {
 
     protected void createActionFactories(ServiceRegistry loggingServices, Collection<CommandLineAction> actions) {
         actions.add(new GuiActionsFactory());
-        actions.add(new BuildActionsFactory(loggingServices));
+        actions.add(new BuildActionsFactory(loggingServices, new ParametersConverter()));
     }
 
     private static GradleLauncherMetaData clientMetaData() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java
index ba54733..a575527 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java
@@ -17,7 +17,7 @@ package org.gradle.launcher.cli;
 
 import org.gradle.api.Action;
 import org.gradle.launcher.bootstrap.ExecutionListener;
-import org.gradle.launcher.exec.ReportedException;
+import org.gradle.initialization.ReportedException;
 
 public class ExceptionReportingAction implements Action<ExecutionListener> {
     private final Action<ExecutionListener> action;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/Parameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/Parameters.java
new file mode 100644
index 0000000..6ad7f35
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/Parameters.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.cli;
+
+import org.gradle.StartParameter;
+import org.gradle.initialization.BuildLayoutParameters;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+
+public class Parameters {
+    private BuildLayoutParameters layout;
+    private StartParameter startParameter;
+    private DaemonParameters daemonParameters;
+
+    public Parameters() {
+        this.layout = new BuildLayoutParameters();
+        this.startParameter = new StartParameter();
+        this.daemonParameters = new DaemonParameters(layout);
+    }
+
+    public Parameters(StartParameter startParameter) {
+        this();
+        this.startParameter = startParameter;
+    }
+
+    public DaemonParameters getDaemonParameters() {
+        return daemonParameters;
+    }
+
+    public StartParameter getStartParameter() {
+        return startParameter;
+    }
+
+    public BuildLayoutParameters getLayout() {
+        return layout;
+    }
+
+    public void setDaemonParameters(DaemonParameters daemonParameters) {
+        this.daemonParameters = daemonParameters;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ParametersConverter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ParametersConverter.java
new file mode 100644
index 0000000..a7ed1ae
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ParametersConverter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.cli;
+
+import org.gradle.cli.*;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.initialization.LayoutCommandLineConverter;
+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.configuration.DaemonParameters;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ParametersConverter extends AbstractCommandLineConverter<Parameters> {
+
+    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;
+
+    ParametersConverter(LayoutCommandLineConverter layoutConverter,
+                        SystemPropertiesCommandLineConverter propertiesConverter,
+                        LayoutToPropertiesConverter layoutToPropertiesConverter,
+                        PropertiesToStartParameterConverter propertiesToStartParameterConverter,
+                        DefaultCommandLineConverter commandLineConverter,
+                        DaemonCommandLineConverter daemonConverter,
+                        PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter) {
+        this.layoutConverter = layoutConverter;
+        this.propertiesConverter = propertiesConverter;
+        this.layoutToPropertiesConverter = layoutToPropertiesConverter;
+        this.propertiesToStartParameterConverter = propertiesToStartParameterConverter;
+        this.commandLineConverter = commandLineConverter;
+        this.daemonConverter = daemonConverter;
+        this.propertiesToDaemonParametersConverter = propertiesToDaemonParametersConverter;
+    }
+
+    public ParametersConverter() {
+        this(new LayoutCommandLineConverter(),
+            new SystemPropertiesCommandLineConverter(),
+            new LayoutToPropertiesConverter(),
+            new PropertiesToStartParameterConverter(),
+            new DefaultCommandLineConverter(),
+            new DaemonCommandLineConverter(),
+            new PropertiesToDaemonParametersConverter());
+    }
+
+    @Override
+    public Parameters convert(ParsedCommandLine args, Parameters target) throws CommandLineArgumentException {
+        layoutConverter.convert(args, target.getLayout());
+
+        Map<String, String> properties = new HashMap<String, String>();
+        layoutToPropertiesConverter.convert(target.getLayout(), properties);
+        propertiesConverter.convert(args, properties);
+
+        propertiesToStartParameterConverter.convert(properties, target.getStartParameter());
+        commandLineConverter.convert(args, target.getStartParameter());
+
+        DaemonParameters daemonParameters = new DaemonParameters(target.getLayout(), target.getStartParameter().getSystemPropertiesArgs());
+        propertiesToDaemonParametersConverter.convert(properties, daemonParameters);
+        daemonConverter.convert(args, daemonParameters);
+        target.setDaemonParameters(daemonParameters);
+
+        return target;
+    }
+
+    @Override
+    public void configure(CommandLineParser parser) {
+        commandLineConverter.configure(parser);
+        daemonConverter.configure(parser);
+    }
+}
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 a4cab50..b345ae0 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
@@ -39,7 +39,7 @@ public class RunBuildAction implements Runnable {
     public void run() {
         executer.execute(
                 new ExecuteBuildAction(startParameter),
-                new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(clientMetaData, startTime), new FixedBuildCancellationToken(), new NoOpBuildEventConsumer()),
+                new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(clientMetaData, startTime), new DefaultBuildCancellationToken(), new NoOpBuildEventConsumer()),
                 buildActionParameters);
     }
 }
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
index a5ce78c..8ab8925 100644
--- 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
@@ -26,8 +26,16 @@ public class DaemonCommandLineConverter extends AbstractCommandLineConverter<Dae
 
     private static final String DAEMON = "daemon";
     private static final String NO_DAEMON = "no-daemon";
+    private static final String FOREGROUND = "foreground";
+    private static final String STOP = "stop";
 
     public DaemonParameters convert(ParsedCommandLine args, DaemonParameters target) throws CommandLineArgumentException {
+        if (args.hasOption(FOREGROUND)) {
+            target.setForeground(true);
+        }
+        if (args.hasOption(STOP)) {
+            target.setStop(true);
+        }
         if (args.hasOption(NO_DAEMON)) {
             return target.setEnabled(false);
         }
@@ -38,6 +46,8 @@ public class DaemonCommandLineConverter extends AbstractCommandLineConverter<Dae
     }
 
     public void configure(CommandLineParser parser) {
+        parser.option(FOREGROUND).hasDescription("Starts the Gradle daemon in the foreground.").incubating();
+        parser.option(STOP).hasDescription("Stops the Gradle daemon if it is running.");
         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/daemon/bootstrap/DaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
index 5d34589..8e861f3 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
@@ -16,10 +16,12 @@
 package org.gradle.launcher.daemon.bootstrap;
 
 import com.google.common.io.Files;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.nativeintegration.services.NativeServices;
+import org.gradle.internal.serialize.kryo.KryoBackedDecoder;
 import org.gradle.launcher.bootstrap.EntryPoint;
 import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.launcher.daemon.configuration.DaemonServerConfiguration;
@@ -31,11 +33,9 @@ import org.gradle.launcher.daemon.server.DaemonServices;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.messaging.remote.Address;
+import org.gradle.process.internal.child.EncodedStream;
 
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.PrintStream;
+import java.io.*;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -57,27 +57,33 @@ public class DaemonMain extends EntryPoint {
     @Override
     protected void doAction(String[] args, ExecutionListener listener) {
         //The first argument is not really used but it is very useful in diagnosing, i.e. running 'jps -m'
-        if (args.length < 4) {
-            invalidArgs("Following arguments are required: <gradle-version> <gradle-home-dir> <daemon-dir> <timeout-millis> <daemonUid> <optional startup jvm opts>");
+        if (args.length != 1) {
+            invalidArgs("Following arguments are required: <gradle-version>");
         }
 
-        File gradleHomeDir = new File(args[1]);
-        File daemonBaseDir = new File(args[2]);
+        // Read configuration from stdin
 
-        int idleTimeoutMs = 0;
+        List<String> startupOpts;
+        File gradleHomeDir;
+        File daemonBaseDir;
+        int idleTimeoutMs;
+        String daemonUid;
+
+        KryoBackedDecoder decoder = new KryoBackedDecoder(new EncodedStream.EncodedInput(System.in));
         try {
-            idleTimeoutMs = Integer.parseInt(args[3]);
-        } catch (NumberFormatException e) {
-            invalidArgs("Second argument must be a whole number (i.e. daemon idle timeout in ms)");
+            gradleHomeDir = new File(decoder.readString());
+            daemonBaseDir = new File(decoder.readString());
+            idleTimeoutMs = decoder.readSmallInt();
+            daemonUid = decoder.readString();
+            int argCount = decoder.readSmallInt();
+            startupOpts = new ArrayList<String>(argCount);
+            for (int i = 0; i < argCount; i++) {
+                startupOpts.add(decoder.readString());
+            }
+        } catch (EOFException e) {
+            throw new UncheckedIOException(e);
         }
 
-        String daemonUid = args[4];
-
-        List<String> startupOpts = new ArrayList<String>(args.length - 5);
-        //noinspection ManualArrayToCollectionCopy
-        for (int i = 5; i < args.length; i++) {
-            startupOpts.add(args[i]);
-        }
         LOGGER.debug("Assuming the daemon was started with following jvm opts: {}", startupOpts);
 
         NativeServices.initialize(gradleHomeDir);
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 9b623d4..762c69b 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
@@ -18,10 +18,12 @@ package org.gradle.launcher.daemon.bootstrap;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.process.internal.streams.ExecOutputHandleRunner;
 import org.gradle.process.internal.streams.StreamsHandler;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -30,32 +32,26 @@ import java.util.Scanner;
 public class DaemonOutputConsumer implements StreamsHandler {
 
     private final static Logger LOGGER = Logging.getLogger(DaemonOutputConsumer.class);
+    private final InputStream stdInput;
 
     private StringWriter output;
     private StoppableExecutor executor;
     private Runnable streamConsumer;
     DaemonStartupCommunication startupCommunication = new DaemonStartupCommunication();
     private String processOutput;
+    private ExecOutputHandleRunner standardInputRunner;
 
-    public void connectStreams(final Process process, String processName) {
+    public DaemonOutputConsumer(InputStream stdInput) {
+        this.stdInput = stdInput;
+    }
+
+    public void connectStreams(final Process process, String processName, ExecutorFactory executorFactory) {
         if (process == null || processName == null) {
             throw new IllegalArgumentException("Cannot connect streams because provided process or its name is null");
         }
+        standardInputRunner = new ExecOutputHandleRunner("write standard input into: " + processName, stdInput, process.getOutputStream());
+        executor = executorFactory.create("Read output from: " + processName);
         final InputStream inputStream = process.getInputStream();
-        connectStreams(inputStream, processName);
-    }
-
-    public void start() {
-        if (executor == null || streamConsumer == null) {
-            throw new IllegalStateException("Cannot start consuming daemon output because streams have not been connected first.");
-        }
-        LOGGER.debug("Starting consuming the daemon process output.");
-        output = new StringWriter();
-        executor.execute(streamConsumer);
-    }
-
-    void connectStreams(final InputStream inputStream, String processName) {
-        executor = new DefaultExecutorFactory().create("Read output from: " + processName);
         streamConsumer = new Runnable() {
             public void run() {
                 Scanner scanner = new Scanner(inputStream);
@@ -76,6 +72,16 @@ public class DaemonOutputConsumer implements StreamsHandler {
         };
     }
 
+    public void start() {
+        if (executor == null || streamConsumer == null) {
+            throw new IllegalStateException("Cannot start consuming daemon output because streams have not been connected first.");
+        }
+        LOGGER.debug("Starting consuming the daemon process output.");
+        output = new StringWriter();
+        executor.execute(standardInputRunner);
+        executor.execute(streamConsumer);
+    }
+
     public String getProcessOutput() {
         if (processOutput == null) {
             throw new IllegalStateException("Unable to get process output as consuming has not finished yet.");
@@ -87,6 +93,11 @@ public class DaemonOutputConsumer implements StreamsHandler {
         if (executor == null || output == null) {
             throw new IllegalStateException("Unable to stop output consumer. Was it started?.");
         }
+        try {
+            standardInputRunner.closeInput();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
         executor.stop();
         processOutput = output.toString();
     }
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 f9e9daa..ea5f668 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,6 @@ import org.gradle.api.BuildCancelledException;
 import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.invocation.BuildAction;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.initialization.BuildEventConsumer;
 import org.gradle.initialization.BuildRequestContext;
@@ -27,6 +26,7 @@ import org.gradle.internal.UncheckedException;
 import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.invocation.BuildAction;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.protocol.*;
@@ -107,7 +107,7 @@ public class DaemonClient implements BuildActionExecuter<BuildActionParameters>
      * Executes the given action in the daemon. The action and parameters must be serializable.
      *
      * @param action The action
-     * @throws org.gradle.launcher.exec.ReportedException On failure, when the failure has already been logged/reported.
+     * @throws org.gradle.initialization.ReportedException On failure, when the failure has already been logged/reported.
      */
     public Object execute(BuildAction action, BuildRequestContext requestContext, BuildActionParameters parameters) {
         Object buildId = idGenerator.generateId();
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 6fd7f16..eb115ff 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
@@ -38,8 +38,12 @@ public class DaemonClientServices extends DaemonClientServicesSupport {
         addProvider(new DaemonRegistryServices(daemonParameters.getBaseDir()));
     }
 
-    DaemonStarter createDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, ListenerManager listenerManager, DaemonGreeter daemonGreeter) {
-        return new DefaultDaemonStarter(daemonDir, daemonParameters, daemonGreeter, listenerManager.getBroadcaster(DaemonStartListener.class));
+    JvmVersionValidator createJvmVersionValidator() {
+        return new JvmVersionValidator();
+    }
+
+    DaemonStarter createDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, ListenerManager listenerManager, DaemonGreeter daemonGreeter, JvmVersionValidator jvmVersionValidator) {
+        return new DefaultDaemonStarter(daemonDir, daemonParameters, daemonGreeter, listenerManager.getBroadcaster(DaemonStartListener.class), jvmVersionValidator);
     }
 
     DaemonGreeter createDaemonGreeter(DocumentationRegistry documentationRegistry) {
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 acb7d84..775d661 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
@@ -37,7 +37,6 @@ import java.io.InputStream;
  * Some support wiring for daemon clients.
  * 
  * @see DaemonClientServices
- * @see EmbeddedDaemonClientServices
  */
 abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry {
     private final InputStream buildStandardInput;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
index 4144ef5..97807ca 100755
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
@@ -16,9 +16,12 @@
 package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.serialize.FlushableEncoder;
+import org.gradle.internal.serialize.kryo.KryoBackedEncoder;
 import org.gradle.launcher.daemon.DaemonExecHandleBuilder;
 import org.gradle.launcher.daemon.bootstrap.DaemonGreeter;
 import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer;
@@ -28,12 +31,13 @@ import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
 import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.child.EncodedStream;
 import org.gradle.util.Clock;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GradleVersion;
 
-import java.io.File;
+import java.io.*;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -47,12 +51,14 @@ public class DefaultDaemonStarter implements DaemonStarter {
     private final DaemonParameters daemonParameters;
     private final DaemonGreeter daemonGreeter;
     private final DaemonStartListener listener;
+    private final JvmVersionValidator versionValidator;
 
-    public DefaultDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, DaemonGreeter daemonGreeter, DaemonStartListener listener) {
+    public DefaultDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, DaemonGreeter daemonGreeter, DaemonStartListener listener, JvmVersionValidator versionValidator) {
         this.daemonDir = daemonDir;
         this.daemonParameters = daemonParameters;
         this.daemonGreeter = daemonGreeter;
         this.listener = listener;
+        this.versionValidator = versionValidator;
     }
 
     public DaemonStartupInfo startDaemon() {
@@ -67,42 +73,56 @@ public class DefaultDaemonStarter implements DaemonStarter {
             throw new IllegalStateException("Unable to construct a bootstrap classpath when starting the daemon");
         }
 
-        new JvmVersionValidator().validate(daemonParameters);
+        versionValidator.validate(daemonParameters);
 
         List<String> daemonArgs = new ArrayList<String>();
-        daemonArgs.add(daemonParameters.getEffectiveJavaExecutable());
+        daemonArgs.add(daemonParameters.getEffectiveJavaExecutable().getAbsolutePath());
 
         List<String> daemonOpts = daemonParameters.getEffectiveJvmArgs();
         LOGGER.debug("Using daemon opts: {}", daemonOpts);
         daemonArgs.addAll(daemonOpts);
-        //Useful for debugging purposes - simply uncomment and connect to debug
-//        daemonArgs.add("-Xdebug");
-//        daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006");
         daemonArgs.add("-cp");
         daemonArgs.add(CollectionUtils.join(File.pathSeparator, bootstrapClasspath));
+
+        if (Boolean.getBoolean("org.gradle.daemon.debug")) {
+            daemonArgs.add("-Xdebug");
+            daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
+        }
+
         daemonArgs.add(GradleDaemon.class.getName());
+        // Version isn't used, except by a human looking at the output of jps.
         daemonArgs.add(GradleVersion.current().getVersion());
-        daemonArgs.add(daemonParameters.getGradleUserHomeDir().getAbsolutePath());
-        daemonArgs.add(daemonDir.getBaseDir().getAbsolutePath());
-        daemonArgs.add(String.valueOf(daemonParameters.getIdleTimeout()));
-        daemonArgs.add(daemonParameters.getUid());
 
-        //all remaining arguments are daemon startup jvm opts.
-        //we need to pass them as *program* arguments to avoid problems with getInputArguments().
-        daemonArgs.addAll(daemonOpts);
+        // Serialize configuration to daemon via the process' stdin
+        ByteArrayOutputStream serializedConfig = new ByteArrayOutputStream();
+        FlushableEncoder encoder = new KryoBackedEncoder(new EncodedStream.EncodedOutput(serializedConfig));
+        try {
+            encoder.writeString(daemonParameters.getGradleUserHomeDir().getAbsolutePath());
+            encoder.writeString(daemonDir.getBaseDir().getAbsolutePath());
+            encoder.writeSmallInt(daemonParameters.getIdleTimeout());
+            encoder.writeString(daemonParameters.getUid());
+            encoder.writeSmallInt(daemonOpts.size());
+            for (String daemonOpt : daemonOpts) {
+                encoder.writeString(daemonOpt);
+            }
+            encoder.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        ByteArrayInputStream stdInput = new ByteArrayInputStream(serializedConfig.toByteArray());
 
-        DaemonStartupInfo daemonInfo = startProcess(daemonArgs, daemonDir.getVersionedDir());
+        DaemonStartupInfo daemonInfo = startProcess(daemonArgs, daemonDir.getVersionedDir(), stdInput);
         listener.daemonStarted(daemonInfo);
         return daemonInfo;
     }
 
-    private DaemonStartupInfo startProcess(final List<String> args, final File workingDir) {
+    private DaemonStartupInfo startProcess(List<String> args, File workingDir, InputStream stdInput) {
         LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
         Clock clock = new Clock();
         try {
             GFileUtils.mkdirs(workingDir);
 
-            DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer();
+            DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer(stdInput);
             ExecHandle handle = new DaemonExecHandleBuilder().build(args, workingDir, outputConsumer);
 
             handle.start();
@@ -120,4 +140,4 @@ public class DefaultDaemonStarter implements DaemonStarter {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
index 77aadd6..900ae22 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
@@ -34,8 +34,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.StopHandlingCommandExecuter;
-import org.gradle.launcher.exec.InProcessBuildActionExecuter;
+import org.gradle.launcher.exec.BuildExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEvent;
@@ -69,13 +68,13 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
 
     protected DaemonCommandExecuter createDaemonCommandExecuter() {
         LoggingManagerInternal mgr = newInstance(LoggingManagerInternal.class);
-        return new StopHandlingCommandExecuter(
-                new DefaultDaemonCommandExecuter(
-                        get(InProcessBuildActionExecuter.class),
-                        get(ProcessEnvironment.class),
-                        mgr,
-                        new File("dummy"),
-                        new StubDaemonHealthServices()));
+        return new DefaultDaemonCommandExecuter(
+            get(BuildExecuter.class),
+            get(ProcessEnvironment.class),
+            mgr,
+            new File("dummy"),
+            new StubDaemonHealthServices()
+        );
     }
 
     public EmbeddedDaemonClientServices(ServiceRegistry loggingServices) {
@@ -89,7 +88,10 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
     }
 
     protected OutputEventListener createOutputEventListener() {
-        return new OutputEventListener() { public void onOutput(OutputEvent event) {} };
+        return new OutputEventListener() {
+            public void onOutput(OutputEvent event) {
+            }
+        };
     }
 
     @Override
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/JvmVersionValidator.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/JvmVersionValidator.java
index 4db188a..f37f21c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/JvmVersionValidator.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/JvmVersionValidator.java
@@ -23,15 +23,31 @@ import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.process.internal.ExecHandleBuilder;
 
 import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class JvmVersionValidator {
+    private final Map<File, JavaVersion> cachedResults = new HashMap<File, JavaVersion>();
+
     void validate(DaemonParameters parameters) {
         if (parameters.getEffectiveJavaHome().equals(Jvm.current().getJavaHome())) {
             return;
         }
 
+        JavaVersion javaVersion = getJavaVersion(parameters);
+        if (!javaVersion.isJava6Compatible()) {
+            throw UnsupportedJavaRuntimeException.configuredWithUnsupportedVersion("Gradle", JavaVersion.VERSION_1_6, javaVersion);
+        }
+    }
+
+    private JavaVersion getJavaVersion(DaemonParameters parameters) {
+        JavaVersion version = cachedResults.get(parameters.getEffectiveJavaExecutable());
+        if (version != null) {
+            return version;
+        }
+
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 
         ExecHandleBuilder builder = new ExecHandleBuilder();
@@ -41,10 +57,9 @@ public class JvmVersionValidator {
         builder.setErrorOutput(outputStream);
         builder.build().start().waitForFinish().assertNormalExitValue();
 
-        JavaVersion javaVersion = parseJavaVersionCommandOutput(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()))));
-        if (!javaVersion.isJava6Compatible()) {
-            throw UnsupportedJavaRuntimeException.configuredWithUnsupportedVersion("Gradle", JavaVersion.VERSION_1_6, javaVersion);
-        }
+        version = parseJavaVersionCommandOutput(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()))));
+        cachedResults.put(parameters.getEffectiveJavaExecutable(), version);
+        return version;
     }
 
     static JavaVersion parseJavaVersionCommandOutput(BufferedReader reader) {
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 e6eaf1a..e8e11b3 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
@@ -31,6 +31,7 @@ public class DaemonParameters {
     static final int DEFAULT_IDLE_TIMEOUT = 3 * 60 * 60 * 1000;
 
     public static final List<String> DEFAULT_JVM_ARGS = ImmutableList.of("-Xmx1024m", "-XX:MaxPermSize=256m", "-XX:+HeapDumpOnOutOfMemoryError");
+    public static final String INTERACTIVE_TOGGLE = "org.gradle.interactive";
 
     private final String uid;
     private final File gradleUserHomeDir;
@@ -40,6 +41,9 @@ public class DaemonParameters {
     private final JvmOptions jvmOptions = new JvmOptions(new IdentityFileResolver());
     private DaemonUsage daemonUsage = DaemonUsage.IMPLICITLY_DISABLED;
     private File javaHome;
+    private boolean foreground;
+    private boolean stop;
+    private boolean interactive = System.console() != null || Boolean.getBoolean(INTERACTIVE_TOGGLE);
 
     public DaemonParameters(BuildLayoutParameters layout) {
         this(layout, Collections.<String, String>emptyMap());
@@ -53,6 +57,10 @@ public class DaemonParameters {
         gradleUserHomeDir = layout.getGradleUserHomeDir();
     }
 
+    public boolean isInteractive() {
+        return interactive;
+    }
+
     public DaemonParameters setEnabled(boolean enabled) {
         daemonUsage = enabled ? DaemonUsage.EXPLICITLY_ENABLED : DaemonUsage.EXPLICITLY_DISABLED;
         return this;
@@ -93,11 +101,16 @@ public class DaemonParameters {
         return javaHome;
     }
 
-    public String getEffectiveJavaExecutable() {
+    public File getEffectiveJavaExecutable() {
         if (javaHome == null) {
-            return Jvm.current().getJavaExecutable().getAbsolutePath();
+            return Jvm.current().getJavaExecutable();
         }
-        return Jvm.forHome(javaHome).getJavaExecutable().getAbsolutePath();
+        return Jvm.forHome(javaHome).getJavaExecutable();
+    }
+
+    public DaemonParameters setInteractive(boolean interactive) {
+        this.interactive = interactive;
+        return this;
     }
 
     public DaemonParameters setJavaHome(File javaHome) {
@@ -138,4 +151,20 @@ public class DaemonParameters {
     public DaemonUsage getDaemonUsage() {
         return daemonUsage;
     }
+
+    public boolean isForeground() {
+        return foreground;
+    }
+
+    public void setForeground(boolean foreground) {
+        this.foreground = foreground;
+    }
+
+    public boolean isStop() {
+        return stop;
+    }
+
+    public void setStop(boolean stop) {
+        this.stop = stop;
+    }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
index 68424ec..3cae8dd 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
@@ -30,10 +30,9 @@ import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
-import org.gradle.launcher.daemon.server.exec.StopHandlingCommandExecuter;
 import org.gradle.launcher.daemon.server.health.DaemonHealthServices;
 import org.gradle.launcher.daemon.server.health.DefaultDaemonHealthServices;
-import org.gradle.launcher.exec.InProcessBuildActionExecuter;
+import org.gradle.launcher.exec.BuildExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.messaging.remote.internal.MessagingServices;
 import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
@@ -65,12 +64,12 @@ public class DaemonServices extends DefaultServiceRegistry {
         builder.setUid(configuration.getUid());
 
         LOGGER.debug("Creating daemon context with opts: {}", configuration.getJvmOptions());
-        
+
         builder.setDaemonOpts(configuration.getJvmOptions());
 
         return builder.create();
     }
-    
+
     public File getDaemonLogFile() {
         final DaemonContext daemonContext = get(DaemonContext.class);
         final Long pid = daemonContext.getPid();
@@ -82,22 +81,24 @@ public class DaemonServices extends DefaultServiceRegistry {
         return new DefaultDaemonHealthServices();
     }
 
-    protected Daemon createDaemon(InProcessBuildActionExecuter buildActionExecuter) {
+    protected Daemon createDaemon(BuildExecuter buildActionExecuter) {
         return new Daemon(
-                new DaemonTcpServerConnector(
-                    get(ExecutorFactory.class),
-                    get(MessagingServices.class).get(InetAddressFactory.class)),
-                get(DaemonRegistry.class),
-                get(DaemonContext.class),
-                "password",
-                new StopHandlingCommandExecuter(
-                        new DefaultDaemonCommandExecuter(
-                                buildActionExecuter,
-                                get(ProcessEnvironment.class),
-                                loggingManager,
-                                getDaemonLogFile(),
-                                get(DaemonHealthServices.class))),
-                get(ExecutorFactory.class));
+            new DaemonTcpServerConnector(
+                get(ExecutorFactory.class),
+                get(MessagingServices.class).get(InetAddressFactory.class)
+            ),
+            get(DaemonRegistry.class),
+            get(DaemonContext.class),
+            "password",
+            new DefaultDaemonCommandExecuter(
+                buildActionExecuter,
+                get(ProcessEnvironment.class),
+                loggingManager,
+                getDaemonLogFile(),
+                get(DaemonHealthServices.class)
+            ),
+            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 e043c3a..95881da 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
@@ -200,7 +200,7 @@ public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
         Date expiry = new Date(waitUntil);
         LOGGER.debug("Cancel requested: will wait for daemon to become idle.");
         try {
-            cancellationToken.doCancel();
+            cancellationToken.cancel();
         } catch (Exception ex) {
             LOGGER.error("Cancel processing failed. Will continue.", ex);
         }
@@ -402,4 +402,4 @@ public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
     boolean isBusy() {
         return state == State.Running && currentCommandExecution != null;
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/api/HandleStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/api/HandleStop.java
new file mode 100644
index 0000000..cad2b2a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/api/HandleStop.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api;
+
+import org.gradle.launcher.daemon.protocol.Stop;
+import org.gradle.launcher.daemon.protocol.StopWhenIdle;
+import org.gradle.launcher.daemon.protocol.Success;
+
+public class HandleStop implements DaemonCommandAction {
+    @Override
+    public void execute(DaemonCommandExecution execution) {
+        if (execution.getCommand() instanceof Stop) {
+            execution.getDaemonStateControl().requestForcefulStop();
+            execution.getConnection().completed(new Success(null));
+        } else if (execution.getCommand() instanceof StopWhenIdle) {
+            execution.getDaemonStateControl().requestStop();
+            execution.getConnection().completed(new Success(null));
+        } else {
+            execution.proceed();
+        }
+    }
+}
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 78967f2..83f1bf7 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
@@ -20,10 +20,7 @@ import org.gradle.internal.nativeintegration.ProcessEnvironment;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.launcher.daemon.server.api.DaemonCommandAction;
-import org.gradle.launcher.daemon.server.api.DaemonCommandExecution;
-import org.gradle.launcher.daemon.server.api.DaemonConnection;
-import org.gradle.launcher.daemon.server.api.DaemonStateControl;
+import org.gradle.launcher.daemon.server.api.*;
 import org.gradle.launcher.daemon.server.health.DaemonHealthServices;
 import org.gradle.launcher.exec.BuildActionExecuter;
 import org.gradle.launcher.exec.BuildActionParameters;
@@ -65,18 +62,19 @@ public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
     protected List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
         DaemonDiagnostics daemonDiagnostics = new DaemonDiagnostics(daemonLog, daemonContext.getPid());
         return ImmutableList.of(
-                new HandleCancel(),
-                new ReturnResult(),
-                new StartBuildOrRespondWithBusy(daemonDiagnostics), // from this point down, the daemon is 'busy'
-                healthServices.getGCHintAction(), //TODO SF needs to happen after the result is returned to the client
-                new EstablishBuildEnvironment(processEnvironment),
-                new LogToClient(loggingOutput, daemonDiagnostics), // from this point down, logging is sent back to the client
-                healthServices.getHealthTrackerAction(),
-                new ForwardClientInput(),
-                new RequestStopIfSingleUsedDaemon(),
-                new ResetDeprecationLogger(),
-                new WatchForDisconnection(),
-                new ExecuteBuild(actionExecuter)
+            new HandleStop(),
+            new HandleCancel(),
+            new ReturnResult(),
+            new StartBuildOrRespondWithBusy(daemonDiagnostics), // from this point down, the daemon is 'busy'
+            healthServices.getGCHintAction(), //TODO SF needs to happen after the result is returned to the client
+            new EstablishBuildEnvironment(processEnvironment),
+            new LogToClient(loggingOutput, daemonDiagnostics), // from this point down, logging is sent back to the client
+            healthServices.getHealthTrackerAction(),
+            new ForwardClientInput(),
+            new RequestStopIfSingleUsedDaemon(),
+            new ResetDeprecationLogger(),
+            new WatchForDisconnection(),
+            new ExecuteBuild(actionExecuter)
         );
     }
-}
\ No newline at end of file
+}
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 4d51e0a..800b0d4 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
@@ -23,7 +23,7 @@ import org.gradle.launcher.daemon.protocol.Build;
 import org.gradle.launcher.daemon.server.api.DaemonCommandExecution;
 import org.gradle.launcher.exec.BuildActionExecuter;
 import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.ReportedException;
+import org.gradle.initialization.ReportedException;
 
 /**
  * Actually executes the build.
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopHandlingCommandExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopHandlingCommandExecuter.java
deleted file mode 100644
index bedf326..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopHandlingCommandExecuter.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.server.exec;
-
-import org.gradle.launcher.daemon.context.DaemonContext;
-import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.launcher.daemon.protocol.Stop;
-import org.gradle.launcher.daemon.protocol.StopWhenIdle;
-import org.gradle.launcher.daemon.protocol.Success;
-import org.gradle.launcher.daemon.server.api.DaemonConnection;
-import org.gradle.launcher.daemon.server.api.DaemonStateControl;
-
-public class StopHandlingCommandExecuter implements DaemonCommandExecuter {
-    private final DaemonCommandExecuter executer;
-
-    public StopHandlingCommandExecuter(DaemonCommandExecuter executer) {
-        this.executer = executer;
-    }
-
-    public void executeCommand(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl) {
-        if (command instanceof Stop) {
-            daemonStateControl.requestForcefulStop();
-            connection.completed(new Success(null));
-        } else if (command instanceof StopWhenIdle) {
-            daemonStateControl.requestStop();
-            connection.completed(new Success(null));
-        } else {
-            executer.executeCommand(connection, command, daemonContext, daemonStateControl);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
index 6f21f67..fb547d7 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
@@ -31,4 +31,8 @@ public interface BuildActionParameters {
     LogLevel getLogLevel();
 
     DaemonUsage getDaemonUsage();
+
+    boolean isContinuous();
+
+    boolean isInteractive();
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildExecuter.java
new file mode 100644
index 0000000..aeb9092
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildExecuter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Marker interface that can be used to obtain the action executer responsible for actually running builds.
+ */
+public interface BuildExecuter extends BuildActionExecuter<BuildActionParameters> {
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ContinuousBuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ContinuousBuildActionExecuter.java
new file mode 100644
index 0000000..5477023
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ContinuousBuildActionExecuter.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.exec;
+
+import org.gradle.api.Action;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.execution.internal.TaskInputsListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.file.FileCollectionInternal;
+import org.gradle.api.internal.file.FileSystemSubset;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.execution.CancellableOperationManager;
+import org.gradle.execution.DefaultCancellableOperationManager;
+import org.gradle.execution.PassThruCancellableOperationManager;
+import org.gradle.initialization.BuildCancellationToken;
+import org.gradle.initialization.BuildRequestContext;
+import org.gradle.initialization.ReportedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.filewatch.DefaultFileSystemChangeWaiter;
+import org.gradle.internal.filewatch.FileSystemChangeWaiter;
+import org.gradle.internal.filewatch.FileWatcherFactory;
+import org.gradle.internal.invocation.BuildAction;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.util.DisconnectableInputStream;
+import org.gradle.util.SingleMessageLogger;
+
+public class ContinuousBuildActionExecuter implements BuildExecuter {
+    private final BuildActionExecuter<BuildActionParameters> delegate;
+    private final ListenerManager listenerManager;
+    private final OperatingSystem operatingSystem;
+    private final FileSystemChangeWaiter waiter;
+    private final ExecutorFactory executorFactory;
+    private final JavaVersion javaVersion;
+    private final StyledTextOutput logger;
+
+    public ContinuousBuildActionExecuter(BuildActionExecuter<BuildActionParameters> delegate, FileWatcherFactory fileWatcherFactory, ListenerManager listenerManager, StyledTextOutputFactory styledTextOutputFactory, ExecutorFactory executorFactory) {
+        this(delegate, listenerManager, styledTextOutputFactory, JavaVersion.current(), OperatingSystem.current(), executorFactory, new DefaultFileSystemChangeWaiter(executorFactory, fileWatcherFactory));
+    }
+
+    ContinuousBuildActionExecuter(BuildActionExecuter<BuildActionParameters> delegate, ListenerManager listenerManager, StyledTextOutputFactory styledTextOutputFactory, JavaVersion javaVersion, OperatingSystem operatingSystem, ExecutorFactory executorFactory, FileSystemChangeWaiter waiter) {
+        this.delegate = delegate;
+        this.listenerManager = listenerManager;
+        this.javaVersion = javaVersion;
+        this.operatingSystem = operatingSystem;
+        this.waiter = waiter;
+        this.executorFactory = executorFactory;
+        this.logger = styledTextOutputFactory.create(ContinuousBuildActionExecuter.class, LogLevel.LIFECYCLE);
+    }
+
+    @Override
+    public Object execute(BuildAction action, BuildRequestContext requestContext, BuildActionParameters actionParameters) {
+        if (actionParameters.isContinuous()) {
+            return executeMultipleBuilds(action, requestContext, actionParameters);
+        } else {
+            return delegate.execute(action, requestContext, actionParameters);
+        }
+    }
+
+    private Object executeMultipleBuilds(BuildAction action, BuildRequestContext requestContext, final BuildActionParameters actionParameters) {
+        if (!javaVersion.isJava7Compatible()) {
+            throw new IllegalStateException("Continuous build requires Java 7 or later.");
+        }
+        SingleMessageLogger.incubatingFeatureUsed("Continuous build");
+
+        BuildCancellationToken cancellationToken = requestContext.getCancellationToken();
+
+        final CancellableOperationManager cancellableOperationManager;
+        if (actionParameters.isInteractive()) {
+            if (!(System.in instanceof DisconnectableInputStream)) {
+                System.setIn(new DisconnectableInputStream(System.in));
+            }
+            DisconnectableInputStream inputStream = (DisconnectableInputStream) System.in;
+            cancellableOperationManager = new DefaultCancellableOperationManager(executorFactory.create("cancel signal monitor"), inputStream, cancellationToken);
+        } else {
+            cancellableOperationManager = new PassThruCancellableOperationManager(cancellationToken);
+        }
+
+        Object lastResult = null;
+        int counter = 0;
+        while (!cancellationToken.isCancellationRequested()) {
+            if (++counter != 1) {
+                // reset the time the build started so the total time makes sense
+                requestContext.getBuildTimeClock().reset();
+                logger.println("Change detected, executing build...").println();
+            }
+
+            FileSystemSubset.Builder fileSystemSubsetBuilder = FileSystemSubset.builder();
+            try {
+                lastResult = executeBuildAndAccumulateInputs(action, requestContext, actionParameters, fileSystemSubsetBuilder);
+            } catch (ReportedException t) {
+                lastResult = t;
+            }
+
+            final FileSystemSubset toWatch = fileSystemSubsetBuilder.build();
+            if (toWatch.isEmpty()) {
+                logger.println().withStyle(StyledTextOutput.Style.Failure).println("Exiting continuous build as no executed tasks declared file system inputs.");
+                if (lastResult instanceof ReportedException) {
+                    throw (ReportedException) lastResult;
+                }
+                return lastResult;
+            } else {
+                cancellableOperationManager.monitorInput(new Action<BuildCancellationToken>() {
+                    @Override
+                    public void execute(BuildCancellationToken cancellationToken) {
+                        waiter.wait(toWatch, cancellationToken, new Runnable() {
+                            @Override
+                            public void run() {
+                                logger.println().println("Waiting for changes to input files of tasks..." + determineExitHint(actionParameters));
+                            }
+                        });
+                    }
+                });
+            }
+        }
+
+        logger.println("Build cancelled.");
+        if (lastResult instanceof ReportedException) {
+            throw (ReportedException) lastResult;
+        }
+        return lastResult;
+    }
+
+    public String determineExitHint(BuildActionParameters actionParameters) {
+        if (actionParameters.isInteractive()) {
+            if (operatingSystem.isWindows()) {
+                return " (ctrl-d then enter to exit)";
+            } else {
+                return " (ctrl-d to exit)";
+            }
+        } else {
+            return "";
+        }
+    }
+
+    private Object executeBuildAndAccumulateInputs(BuildAction action, BuildRequestContext requestContext, BuildActionParameters actionParameters, final FileSystemSubset.Builder fileSystemSubsetBuilder) {
+        TaskInputsListener listener = new TaskInputsListener() {
+            @Override
+            public void onExecute(TaskInternal taskInternal, FileCollectionInternal fileSystemInputs) {
+                fileSystemInputs.registerWatchPoints(fileSystemSubsetBuilder);
+            }
+        };
+        listenerManager.addListener(listener);
+        try {
+            return delegate.execute(action, requestContext, actionParameters);
+        } finally {
+            listenerManager.removeListener(listener);
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
index 4868e31..6996abc 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
@@ -30,16 +30,20 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
     private final Map<String, String> systemProperties;
     private final Map<String, String> envVariables;
     private final DaemonUsage daemonUsage;
+    private final boolean continuous;
+    private final boolean interactive;
 
-    public DefaultBuildActionParameters(Map<?, ?> systemProperties, Map<String, String> envVariables, File currentDir, LogLevel logLevel, DaemonUsage daemonUsage) {
+    public DefaultBuildActionParameters(Map<?, ?> systemProperties, Map<String, String> envVariables, File currentDir, LogLevel logLevel, DaemonUsage daemonUsage, boolean continuous, boolean interactive) {
         this.currentDir = currentDir;
         this.logLevel = logLevel;
+        this.continuous = continuous;
         assert systemProperties != null;
         assert envVariables != null;
         this.systemProperties = new HashMap<String, String>();
         GUtil.addToMap(this.systemProperties, systemProperties);
         this.envVariables = new HashMap<String, String>(envVariables);
         this.daemonUsage = daemonUsage;
+        this.interactive = interactive;
     }
 
     public Map<String, String> getSystemProperties() {
@@ -64,6 +68,10 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
                 + ", currentDir=" + currentDir
                 + ", systemProperties size=" + systemProperties.size()
                 + ", envVariables size=" + envVariables.size()
+                + ", logLevel=" + logLevel
+                + ", daemonUsage=" + daemonUsage
+                + ", continuous=" + continuous
+                + ", interactive=" + interactive
                 + '}';
     }
 
@@ -71,4 +79,12 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
     public DaemonUsage getDaemonUsage() {
         return daemonUsage;
     }
+
+    public boolean isContinuous() {
+        return continuous;
+    }
+
+    public boolean isInteractive() {
+        return interactive;
+    }
 }
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
index 93488c7..e9e7096 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessBuildActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessBuildActionExecuter.java
@@ -16,7 +16,6 @@
 
 package org.gradle.launcher.exec;
 
-import org.gradle.BuildResult;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.initialization.BuildRequestContext;
 import org.gradle.initialization.DefaultGradleLauncher;
@@ -36,6 +35,9 @@ public class InProcessBuildActionExecuter implements BuildActionExecuter<BuildAc
 
     public Object execute(BuildAction action, BuildRequestContext buildRequestContext, BuildActionParameters actionParameters) {
         DefaultGradleLauncher gradleLauncher = (DefaultGradleLauncher) gradleLauncherFactory.newInstance(action.getStartParameter(), buildRequestContext);
+        gradleLauncher.addStandardOutputListener(buildRequestContext.getOutputListener());
+        gradleLauncher.addStandardErrorListener(buildRequestContext.getErrorListener());
+
         try {
             DefaultBuildController buildController = new DefaultBuildController(gradleLauncher);
             buildActionRunner.run(action, buildController);
@@ -87,19 +89,19 @@ public class InProcessBuildActionExecuter implements BuildActionExecuter<BuildAc
         }
 
         public GradleInternal run() {
-            return check(getLauncher().run());
+            try {
+                return (GradleInternal) getLauncher().run().getGradle();
+            } finally {
+                state = State.Completed;
+            }
         }
 
         public GradleInternal configure() {
-            return check(getLauncher().getBuildAnalysis());
-        }
-
-        private GradleInternal check(BuildResult buildResult) {
-            state = State.Completed;
-            if (buildResult.getFailure() != null) {
-                throw new ReportedException(buildResult.getFailure());
+            try {
+                return (GradleInternal) getLauncher().getBuildAnalysis().getGradle();
+            } finally {
+                state = State.Completed;
             }
-            return (GradleInternal) buildResult.getGradle();
         }
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java
deleted file mode 100644
index a1872de..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.launcher.exec;
-
-/**
- * Wraps an exception which has already been logged, and should not be logged again.
- */
-public class ReportedException extends RuntimeException {
-    public ReportedException(Throwable throwable) {
-        super(throwable);
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/DefaultBuildInvocations.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/DefaultBuildInvocations.java
deleted file mode 100644
index acb48db..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/DefaultBuildInvocations.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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;
-import java.util.List;
-
-/**
- * Implementation of {@link org.gradle.tooling.model.gradle.BuildInvocations}
- */
-public class DefaultBuildInvocations implements Serializable {
-    private List<? extends LaunchableGradleTaskSelector> selectors;
-    private List<? extends LaunchableGradleTask> tasks;
-
-    public DefaultBuildInvocations setSelectors(List<? extends LaunchableGradleTaskSelector> selectors) {
-        this.selectors = selectors;
-        return this;
-    }
-
-    public List<? extends LaunchableGradleTaskSelector> getTaskSelectors() {
-        return selectors;
-    }
-
-    public DefaultBuildInvocations setTasks(List<? extends LaunchableGradleTask> tasks) {
-        this.tasks = tasks;
-        return this;
-    }
-
-    public List<? extends LaunchableGradleTask> getTasks() {
-        return tasks;
-    }
-}
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
deleted file mode 100644
index 07c9c52..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleProjectTask.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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
deleted file mode 100644
index dce444e..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTask.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.TaskExecutionRequest;
-import org.gradle.tooling.internal.protocol.InternalLaunchable;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-public class LaunchableGradleTask implements Serializable, InternalLaunchable, TaskExecutionRequest {
-
-    private String path;
-    private String name;
-    private String description;
-    private String displayName;
-    private boolean isPublic;
-
-    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 List<String> getArgs() {
-        return Collections.singletonList(path);
-    }
-
-    public String getProjectPath() {
-        return null;
-    }
-
-    public boolean isPublic() {
-        return isPublic;
-    }
-
-    public LaunchableGradleTask setPublic(boolean isPublic) {
-        this.isPublic = isPublic;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getSimpleName() + "{path='" + path + "',public=" + isPublic + "}";
-    }
-}
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
deleted file mode 100644
index 0136502..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/impl/LaunchableGradleTaskSelector.java
+++ /dev/null
@@ -1,100 +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.tooling.internal.impl;
-
-import org.gradle.TaskExecutionRequest;
-import org.gradle.api.Nullable;
-import org.gradle.tooling.internal.protocol.InternalLaunchable;
-import org.gradle.tooling.model.TaskSelector;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Data used for {@link org.gradle.tooling.model.TaskSelector}.
- */
-public class LaunchableGradleTaskSelector implements TaskSelector, InternalLaunchable, TaskExecutionRequest, Serializable {
-    private String name;
-    private String displayName;
-    private String description;
-    private String taskName;
-    private String projectPath;
-    private boolean isPublic;
-
-    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 List<String> getArgs() {
-        return Collections.singletonList(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;
-    }
-
-    public boolean isPublic() {
-        return isPublic;
-    }
-
-    public LaunchableGradleTaskSelector setPublic(boolean isPublic) {
-        this.isPublic = isPublic;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "LaunchableGradleTaskSelector{"
-                + "name='" + name + "' "
-                + "description='" + description + "'}";
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildClientSubscriptions.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildClientSubscriptions.java
new file mode 100644
index 0000000..94c3783
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/BuildClientSubscriptions.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+/**
+ * Provides information about what events the build client is interested in.
+ */
+public class BuildClientSubscriptions implements Serializable {
+
+    private final boolean sendTestProgressEvents;
+    private final boolean sendTaskProgressEvents;
+    private final boolean sendBuildProgressEvents;
+
+    public BuildClientSubscriptions(boolean sendTestProgressEvents, boolean sendTaskProgressEvents, boolean sendBuildProgressEvents) {
+        this.sendTestProgressEvents = sendTestProgressEvents;
+        this.sendTaskProgressEvents = sendTaskProgressEvents;
+        this.sendBuildProgressEvents = sendBuildProgressEvents;
+    }
+
+    public boolean isSendTestProgressEvents() {
+        return sendTestProgressEvents;
+    }
+
+    public boolean isSendTaskProgressEvents() {
+        return sendTaskProgressEvents;
+    }
+
+    public boolean isSendBuildProgressEvents() {
+        return sendBuildProgressEvents;
+    }
+
+    public boolean isSendAnyProgressEvents() {
+        return sendTestProgressEvents || sendTaskProgressEvents || sendBuildProgressEvents;
+    }
+
+}
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
index 1b3b946..8b0c24d 100644
--- 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
@@ -24,13 +24,13 @@ public class BuildModelAction implements BuildAction, Serializable {
     private final StartParameter startParameter;
     private final String modelName;
     private final boolean runTasks;
-    private final boolean sendTestProgressEvents;
+    private final BuildClientSubscriptions clientSubscriptions;
 
-    public BuildModelAction(StartParameter startParameter, String modelName, boolean runTasks, boolean sendTestProgressEvents) {
+    public BuildModelAction(StartParameter startParameter, String modelName, boolean runTasks, BuildClientSubscriptions clientSubscriptions) {
         this.startParameter = startParameter;
         this.modelName = modelName;
         this.runTasks = runTasks;
-        this.sendTestProgressEvents = sendTestProgressEvents;
+        this.clientSubscriptions = clientSubscriptions;
     }
 
     @Override
@@ -46,7 +46,7 @@ public class BuildModelAction implements BuildAction, Serializable {
         return runTasks;
     }
 
-    public boolean isSendTestProgressEvents() {
-        return sendTestProgressEvents;
+    public BuildClientSubscriptions getClientSubscriptions() {
+        return clientSubscriptions;
     }
 }
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
index 6e6dfa3..1ebc628 100644
--- 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
@@ -22,12 +22,14 @@ import org.gradle.internal.invocation.BuildAction;
 import java.io.Serializable;
 
 public class ClientProvidedBuildAction implements BuildAction, Serializable {
-    private final SerializedPayload action;
     private final StartParameter startParameter;
+    private final SerializedPayload action;
+    private final BuildClientSubscriptions clientSubscriptions;
 
-    public ClientProvidedBuildAction(StartParameter startParameter, SerializedPayload action) {
+    public ClientProvidedBuildAction(StartParameter startParameter, SerializedPayload action, BuildClientSubscriptions clientSubscriptions) {
         this.startParameter = startParameter;
         this.action = action;
+        this.clientSubscriptions = clientSubscriptions;
     }
 
     @Override
@@ -38,4 +40,8 @@ public class ClientProvidedBuildAction implements BuildAction, Serializable {
     public SerializedPayload getAction() {
         return action;
     }
+
+    public BuildClientSubscriptions getClientSubscriptions() {
+        return clientSubscriptions;
+    }
 }
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
index 6681268..e617bb0 100644
--- 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
@@ -22,7 +22,7 @@ import org.gradle.internal.service.ServiceRegistration;
 import org.gradle.internal.service.scopes.GlobalScopeServices;
 import org.gradle.launcher.daemon.client.DaemonClientFactory;
 import org.gradle.launcher.daemon.client.DaemonClientGlobalServices;
-import org.gradle.launcher.exec.InProcessBuildActionExecuter;
+import org.gradle.launcher.exec.BuildExecuter;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEventRenderer;
 import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
@@ -49,7 +49,7 @@ public class ConnectionScopeServices {
         return shutdownCoordinator;
     }
 
-    ProviderConnection createProviderConnection(InProcessBuildActionExecuter buildActionExecuter, DaemonClientFactory daemonClientFactory,
+    ProviderConnection createProviderConnection(BuildExecuter buildActionExecuter, DaemonClientFactory daemonClientFactory,
                                                 ClassLoaderFactory classLoaderFactory, ClassLoaderCache classLoaderCache, ShutdownCoordinator shutdownCoordinator) {
         return new ProviderConnection(
                 loggingServices,
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
index 31acb68..eda1285 100644
--- 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
@@ -16,16 +16,19 @@
 package org.gradle.tooling.internal.provider;
 
 import org.gradle.api.BuildCancelledException;
-import org.gradle.internal.invocation.BuildAction;
 import org.gradle.initialization.BuildRequestContext;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.invocation.BuildAction;
 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.initialization.ReportedException;
+import org.gradle.tooling.UnsupportedVersionException;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
 import org.gradle.tooling.internal.protocol.InternalBuildCancelledException;
+import org.gradle.tooling.internal.protocol.InternalCancellationToken;
+import org.gradle.tooling.internal.protocol.ModelIdentifier;
 import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 
 public class DaemonBuildActionExecuter implements BuildActionExecuter<ProviderOperationParameters> {
@@ -38,8 +41,12 @@ public class DaemonBuildActionExecuter implements BuildActionExecuter<ProviderOp
     }
 
     public Object execute(BuildAction action, BuildRequestContext buildRequestContext, ProviderOperationParameters parameters) {
+        boolean continuous = action.getStartParameter() != null && action.getStartParameter().isContinuous() && isNotBuildingModel(action);
+        if (continuous && !doesConsumerSupportCancellation(buildRequestContext)) {
+            throw new UnsupportedVersionException("Continuous build requires Tooling API client version 2.1 or later.");
+        }
         BuildActionParameters actionParameters = new DefaultBuildActionParameters(daemonParameters.getEffectiveSystemProperties(),
-                System.getenv(), SystemProperties.getInstance().getCurrentDir(), parameters.getBuildLogLevel(), daemonParameters.getDaemonUsage());
+            System.getenv(), SystemProperties.getInstance().getCurrentDir(), parameters.getBuildLogLevel(), daemonParameters.getDaemonUsage(), continuous, false);
         try {
             return executer.execute(action, buildRequestContext, actionParameters);
         } catch (ReportedException e) {
@@ -53,4 +60,18 @@ public class DaemonBuildActionExecuter implements BuildActionExecuter<ProviderOp
             throw new BuildExceptionVersion1(e.getCause());
         }
     }
+
+    protected boolean doesConsumerSupportCancellation(BuildRequestContext buildRequestContext) {
+        // cancellation token will be instanceof InternalCancellationToken when consumer supports cancellation
+        return buildRequestContext.getCancellationToken() instanceof InternalCancellationToken;
+    }
+
+    private boolean isNotBuildingModel(BuildAction action) {
+        if (!(action instanceof BuildModelAction)) {
+            return true;
+        }
+        String modelName = ((BuildModelAction) action).getModelName();
+        return modelName.equals(ModelIdentifier.NULL_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 19d37c0..d59832d 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
@@ -18,7 +18,8 @@ package org.gradle.tooling.internal.provider;
 import org.gradle.api.JavaVersion;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.initialization.BuildLayoutParameters;
-import org.gradle.initialization.FixedBuildCancellationToken;
+import org.gradle.initialization.DefaultBuildCancellationToken;
+import org.gradle.internal.Cast;
 import org.gradle.internal.concurrent.CompositeStoppable;
 import org.gradle.internal.jvm.UnsupportedJavaRuntimeException;
 import org.gradle.internal.nativeintegration.services.NativeServices;
@@ -41,7 +42,7 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 
 public class DefaultConnection implements InternalConnection, BuildActionRunner,
-        ConfigurableConnection, ModelBuilder, InternalBuildActionExecutor, InternalCancellableConnection, StoppableConnection {
+    ConfigurableConnection, ModelBuilder, InternalBuildActionExecutor, InternalCancellableConnection, StoppableConnection {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnection.class);
     private ProtocolToModelAdapter adapter;
     private ServiceRegistry services;
@@ -71,10 +72,10 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         NativeServices.initialize(gradleUserHomeDir);
         LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
         services = ServiceRegistryBuilder.builder()
-                .displayName("Connection services")
-                .parent(loggingServices)
-                .parent(NativeServices.getInstance())
-                .provider(new ConnectionScopeServices(loggingServices)).build();
+            .displayName("Connection services")
+            .parent(loggingServices)
+            .parent(NativeServices.getInstance())
+            .provider(new ConnectionScopeServices(loggingServices)).build();
         adapter = services.get(ProtocolToModelAdapter.class);
         connection = services.get(ProviderConnection.class);
     }
@@ -140,7 +141,7 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         validateCanRun();
         ProviderOperationParameters providerParameters = toProviderParameters(buildParameters);
         String modelName = new ModelMapping().getModelNameFromProtocolType(type);
-        T result = (T) connection.run(modelName, new FixedBuildCancellationToken(), providerParameters);
+        T result = Cast.uncheckedCast(connection.run(modelName, new DefaultBuildCancellationToken(), providerParameters));
         return new ProviderBuildResult<T>(result);
     }
 
@@ -150,7 +151,7 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
     public BuildResult<?> getModel(ModelIdentifier modelIdentifier, BuildParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
         validateCanRun();
         ProviderOperationParameters providerParameters = toProviderParameters(operationParameters);
-        Object result = connection.run(modelIdentifier.getName(), new FixedBuildCancellationToken(), providerParameters);
+        Object result = connection.run(modelIdentifier.getName(), new DefaultBuildCancellationToken(), providerParameters);
         return new ProviderBuildResult<Object>(result);
     }
 
@@ -171,7 +172,7 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
     public <T> BuildResult<T> run(InternalBuildAction<T> action, BuildParameters operationParameters) throws BuildExceptionVersion1, InternalUnsupportedBuildArgumentException, IllegalStateException {
         validateCanRun();
         ProviderOperationParameters providerParameters = toProviderParameters(operationParameters);
-        Object results = connection.run(action, new FixedBuildCancellationToken(), providerParameters);
+        Object results = connection.run(action, new DefaultBuildCancellationToken(), providerParameters);
         return new ProviderBuildResult<T>((T) results);
     }
 
@@ -179,7 +180,7 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
      * This is used by consumers 2.1-rc-1 and later.
      */
     public <T> BuildResult<T> run(InternalBuildAction<T> action, InternalCancellationToken cancellationToken, BuildParameters operationParameters)
-            throws BuildExceptionVersion1, InternalUnsupportedBuildArgumentException, IllegalStateException {
+        throws BuildExceptionVersion1, InternalUnsupportedBuildArgumentException, IllegalStateException {
         validateCanRun();
         ProviderOperationParameters providerParameters = toProviderParameters(operationParameters);
         BuildCancellationToken buildCancellationToken = new InternalCancellationTokenAdapter(cancellationToken);
@@ -201,4 +202,4 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
     private ProviderOperationParameters toProviderParameters(BuildParameters buildParameters) {
         return adapter.adapt(ProviderOperationParameters.class, buildParameters, BuildLogLevelMixIn.class);
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/InternalCancellationTokenAdapter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/InternalCancellationTokenAdapter.java
index 79b1194..a33382b 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/InternalCancellationTokenAdapter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/InternalCancellationTokenAdapter.java
@@ -19,7 +19,7 @@ package org.gradle.tooling.internal.provider;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.tooling.internal.protocol.InternalCancellationToken;
 
-public class InternalCancellationTokenAdapter implements BuildCancellationToken {
+public class InternalCancellationTokenAdapter implements BuildCancellationToken, InternalCancellationToken {
     private final InternalCancellationToken cancellationToken;
 
     public InternalCancellationTokenAdapter(InternalCancellationToken cancellationToken) {
@@ -37,4 +37,9 @@ public class InternalCancellationTokenAdapter implements BuildCancellationToken
     public void removeCallback(Runnable cancellationHandler) {
         cancellationToken.removeCallback(cancellationHandler);
     }
+
+    @Override
+    public void cancel() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LauncherServices.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LauncherServices.java
index da2bea7..b3a8070 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LauncherServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LauncherServices.java
@@ -19,12 +19,15 @@ package org.gradle.tooling.internal.provider;
 import org.gradle.cache.CacheRepository;
 import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.internal.classloader.ClassLoaderFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.event.ListenerManager;
+import org.gradle.internal.filewatch.FileWatcherFactory;
 import org.gradle.internal.invocation.BuildActionRunner;
 import org.gradle.internal.service.ServiceRegistration;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.scopes.PluginServiceRegistry;
-import org.gradle.launcher.exec.ChainingBuildActionRunner;
-import org.gradle.launcher.exec.InProcessBuildActionExecuter;
+import org.gradle.launcher.exec.*;
+import org.gradle.logging.StyledTextOutputFactory;
 
 import java.util.List;
 
@@ -44,9 +47,10 @@ public class LauncherServices implements PluginServiceRegistry {
     }
 
     static class ToolingGlobalScopeServices {
-        InProcessBuildActionExecuter createBuildActionExecuter(GradleLauncherFactory gradleLauncherFactory, ServiceRegistry services) {
+        BuildExecuter createBuildExecuter(GradleLauncherFactory gradleLauncherFactory, ServiceRegistry services, ListenerManager listenerManager, FileWatcherFactory fileWatcherFactory, ExecutorFactory executorFactory, StyledTextOutputFactory styledTextOutputFactory) {
             List<BuildActionRunner> buildActionRunners = services.getAll(BuildActionRunner.class);
-            return new InProcessBuildActionExecuter(gradleLauncherFactory, new ChainingBuildActionRunner(buildActionRunners));
+            BuildActionExecuter<BuildActionParameters> delegate = new InProcessBuildActionExecuter(gradleLauncherFactory, new ChainingBuildActionRunner(buildActionRunners));
+            return new ContinuousBuildActionExecuter(delegate, fileWatcherFactory, listenerManager, styledTextOutputFactory, executorFactory);
         }
 
         ExecuteBuildActionRunner createExecuteBuildActionRunner() {
@@ -65,17 +69,17 @@ public class LauncherServices implements PluginServiceRegistry {
     static class ToolingBuildScopeServices {
         PayloadClassLoaderFactory createClassLoaderFactory(ClassLoaderFactory classLoaderFactory, JarCache jarCache, CacheRepository cacheRepository) {
             return new DaemonSidePayloadClassLoaderFactory(
-                    new ModelClassLoaderFactory(
-                            classLoaderFactory),
-                    jarCache,
-                    cacheRepository);
+                new ModelClassLoaderFactory(
+                    classLoaderFactory),
+                jarCache,
+                cacheRepository);
         }
 
         PayloadSerializer createPayloadSerializer(ClassLoaderCache classLoaderCache, PayloadClassLoaderFactory classLoaderFactory) {
             return new PayloadSerializer(
-                    new DefaultPayloadClassLoaderRegistry(
-                            classLoaderCache,
-                            classLoaderFactory)
+                new DefaultPayloadClassLoaderRegistry(
+                    classLoaderCache,
+                    classLoaderFactory)
             );
         }
     }
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
index 05f10ad..be236a6 100644
--- 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
@@ -35,9 +35,13 @@ import org.gradle.logging.internal.OutputEventListener;
 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.parameters.FailsafeBuildProgressListenerAdapter;
 import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
-import org.gradle.tooling.internal.protocol.*;
-import org.gradle.tooling.internal.protocol.events.InternalTestProgressEvent;
+import org.gradle.tooling.internal.protocol.InternalBuildAction;
+import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener;
+import org.gradle.tooling.internal.protocol.ModelIdentifier;
+import org.gradle.tooling.internal.protocol.events.InternalProgressEvent;
 import org.gradle.tooling.internal.provider.connection.ProviderConnectionParameters;
 import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 import org.gradle.util.GradleVersion;
@@ -91,30 +95,32 @@ public class ProviderConnection {
         }
 
         StartParameter startParameter = new ProviderStartParameterConverter().toStartParameter(providerParameters, params.properties);
-        InternalBuildProgressListener buildProgressListener = providerParameters.getBuildProgressListener(null);
-        boolean listenToTestProgress = buildProgressListener != null && buildProgressListener.getSubscribedOperations().contains(InternalBuildProgressListener.TEST_EXECUTION);
-        BuildEventConsumer buildEventConsumer = listenToTestProgress ? new BuildProgressListenerInvokingBuildEventConsumer(buildProgressListener) : new NoOpBuildEventConsumer();
-        BuildAction action = new BuildModelAction(startParameter, modelName, tasks != null, listenToTestProgress);
-        return run(action, cancellationToken, buildEventConsumer, providerParameters, params);
+        ProgressListenerConfiguration listenerConfig = ProgressListenerConfiguration.from(providerParameters);
+        BuildAction action = new BuildModelAction(startParameter, modelName, tasks != null, listenerConfig.clientSubscriptions);
+        return run(action, cancellationToken, listenerConfig, providerParameters, params);
     }
 
     public Object run(InternalBuildAction<?> clientAction, BuildCancellationToken cancellationToken, ProviderOperationParameters providerParameters) {
         SerializedPayload serializedAction = payloadSerializer.serialize(clientAction);
         Parameters params = initParams(providerParameters);
         StartParameter startParameter = new ProviderStartParameterConverter().toStartParameter(providerParameters, params.properties);
-        NoOpBuildEventConsumer buildEventConsumer = new NoOpBuildEventConsumer();
-        BuildAction action = new ClientProvidedBuildAction(startParameter, serializedAction);
-        return run(action, cancellationToken, buildEventConsumer, providerParameters, params);
+        ProgressListenerConfiguration listenerConfig = ProgressListenerConfiguration.from(providerParameters);
+        BuildAction action = new ClientProvidedBuildAction(startParameter, serializedAction, listenerConfig.clientSubscriptions);
+        return run(action, cancellationToken, listenerConfig, providerParameters, params);
     }
 
-    private Object run(BuildAction action, BuildCancellationToken cancellationToken, BuildEventConsumer buildEventConsumer, ProviderOperationParameters providerParameters, Parameters parameters) {
-        BuildActionExecuter<ProviderOperationParameters> executer = createExecuter(providerParameters, parameters);
-        BuildRequestContext buildRequestContext = new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(providerParameters.getStartTime()), cancellationToken, buildEventConsumer);
-        BuildActionResult result = (BuildActionResult) executer.execute(action, buildRequestContext, providerParameters);
-        if (result.failure != null) {
-            throw (RuntimeException) payloadSerializer.deserialize(result.failure);
+    private Object run(BuildAction action, BuildCancellationToken cancellationToken, ProgressListenerConfiguration progressListenerConfiguration, ProviderOperationParameters providerParameters, Parameters parameters) {
+        try {
+            BuildActionExecuter<ProviderOperationParameters> executer = createExecuter(providerParameters, parameters);
+            BuildRequestContext buildRequestContext = new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(providerParameters.getStartTime()), cancellationToken, progressListenerConfiguration.buildEventConsumer);
+            BuildActionResult result = (BuildActionResult) executer.execute(action, buildRequestContext, providerParameters);
+            if (result.failure != null) {
+                throw (RuntimeException) payloadSerializer.deserialize(result.failure);
+            }
+            return payloadSerializer.deserialize(result.result);
+        } finally {
+            progressListenerConfiguration.failsafeWrapper.rethrowErrors();
         }
-        return payloadSerializer.deserialize(result.result);
     }
 
     private BuildActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters, Parameters params) {
@@ -177,7 +183,6 @@ public class ProviderConnection {
     }
 
     private static final class BuildProgressListenerInvokingBuildEventConsumer implements BuildEventConsumer {
-
         private final InternalBuildProgressListener buildProgressListener;
 
         private BuildProgressListenerInvokingBuildEventConsumer(InternalBuildProgressListener buildProgressListener) {
@@ -186,10 +191,33 @@ public class ProviderConnection {
 
         @Override
         public void dispatch(Object event) {
-            if (event instanceof InternalTestProgressEvent) {
+            if (event instanceof InternalProgressEvent) {
                 this.buildProgressListener.onEvent(event);
             }
         }
     }
 
+    private static final class ProgressListenerConfiguration {
+        private final BuildClientSubscriptions clientSubscriptions;
+        private final FailsafeBuildProgressListenerAdapter failsafeWrapper;
+        private final BuildEventConsumer buildEventConsumer;
+
+        public ProgressListenerConfiguration(BuildClientSubscriptions clientSubscriptions, BuildEventConsumer buildEventConsumer, FailsafeBuildProgressListenerAdapter failsafeWrapper) {
+            this.clientSubscriptions = clientSubscriptions;
+            this.buildEventConsumer = buildEventConsumer;
+            this.failsafeWrapper = failsafeWrapper;
+        }
+
+        private static ProgressListenerConfiguration from(ProviderOperationParameters providerParameters) {
+            InternalBuildProgressListener buildProgressListener = providerParameters.getBuildProgressListener(null);
+            boolean listenToTestProgress = buildProgressListener != null && buildProgressListener.getSubscribedOperations().contains(InternalBuildProgressListener.TEST_EXECUTION);
+            boolean listenToTaskProgress = buildProgressListener != null && buildProgressListener.getSubscribedOperations().contains(InternalBuildProgressListener.TASK_EXECUTION);
+            boolean listenToBuildProgress = buildProgressListener != null && buildProgressListener.getSubscribedOperations().contains(InternalBuildProgressListener.BUILD_EXECUTION);
+            BuildClientSubscriptions clientSubscriptions = new BuildClientSubscriptions(listenToTestProgress, listenToTaskProgress, listenToBuildProgress);
+            FailsafeBuildProgressListenerAdapter wrapper = new FailsafeBuildProgressListenerAdapter(buildProgressListener);
+            BuildEventConsumer buildEventConsumer = clientSubscriptions.isSendAnyProgressEvents()
+                ? new BuildProgressListenerInvokingBuildEventConsumer(wrapper) : new NoOpBuildEventConsumer();
+            return new ProgressListenerConfiguration(clientSubscriptions, buildEventConsumer, wrapper);
+        }
+    }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractOperationResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractOperationResult.java
new file mode 100644
index 0000000..90fc3ea
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractOperationResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationResult;
+
+public abstract class AbstractOperationResult extends AbstractResult implements InternalOperationResult {
+    protected AbstractOperationResult(long startTime, long endTime, String outcomeDescription) {
+        super(startTime, endTime, outcomeDescription);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractProgressEvent.java
new file mode 100644
index 0000000..56e7051
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractProgressEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationDescriptor;
+import org.gradle.tooling.internal.protocol.events.InternalProgressEvent;
+
+import java.io.Serializable;
+
+public abstract class AbstractProgressEvent<T extends InternalOperationDescriptor> implements Serializable, InternalProgressEvent {
+    private final long eventTime;
+    private final T descriptor;
+
+    protected AbstractProgressEvent(long eventTime, T descriptor) {
+        this.eventTime = eventTime;
+        this.descriptor = descriptor;
+    }
+
+    public long getEventTime() {
+        return eventTime;
+    }
+
+    public T getDescriptor() {
+        return descriptor;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractResult.java
new file mode 100644
index 0000000..2d6ce54
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractResult.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractResult implements Serializable {
+    private final long startTime;
+    private final long endTime;
+    private final String outcomeDescription;
+
+    public AbstractResult(long startTime, long endTime, String outcomeDescription) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.outcomeDescription = outcomeDescription;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public String getOutcomeDescription() {
+        return outcomeDescription;
+    }
+
+    public List<DefaultFailure> getFailures() {
+        return Collections.emptyList();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTaskResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTaskResult.java
new file mode 100644
index 0000000..0d18c52
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTaskResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalTaskResult;
+
+public abstract class AbstractTaskResult extends AbstractResult implements InternalTaskResult {
+    protected AbstractTaskResult(long startTime, long endTime, String outcomeDescription) {
+        super(startTime, endTime, outcomeDescription);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestProgressEvent.java
deleted file mode 100644
index aff5870..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestProgressEvent.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
-
-import org.gradle.tooling.internal.protocol.events.InternalTestProgressEvent;
-
-import java.io.Serializable;
-
-public abstract class AbstractTestProgressEvent implements Serializable, InternalTestProgressEvent {
-    private final long eventTime;
-    private final DefaultTestDescriptor descriptor;
-
-    protected AbstractTestProgressEvent(long eventTime, DefaultTestDescriptor descriptor) {
-        this.eventTime = eventTime;
-        this.descriptor = descriptor;
-    }
-
-    public long getEventTime() {
-        return eventTime;
-    }
-
-    public DefaultTestDescriptor getDescriptor() {
-        return descriptor;
-    }
-
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestResult.java
index d966d10..57295d8 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestResult.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/AbstractTestResult.java
@@ -18,31 +18,8 @@ package org.gradle.tooling.internal.provider.events;
 
 import org.gradle.tooling.internal.protocol.events.InternalTestResult;
 
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-public abstract class AbstractTestResult implements Serializable, InternalTestResult {
-    private final long startTime;
-    private final long endTime;
-
-    public AbstractTestResult(long startTime, long endTime) {
-        this.startTime = startTime;
-        this.endTime = endTime;
-    }
-
-    public abstract String getOutcomeDescription();
-
-    public long getStartTime() {
-        return startTime;
+public abstract class AbstractTestResult extends AbstractResult implements InternalTestResult {
+    protected AbstractTestResult(long startTime, long endTime, String outcomeDescription) {
+        super(startTime, endTime, outcomeDescription);
     }
-
-    public long getEndTime() {
-        return endTime;
-    }
-
-    public List<DefaultFailure> getFailures() {
-        return Collections.emptyList();
-    }
-
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultFailureResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultFailureResult.java
new file mode 100644
index 0000000..cd3a5d4
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultFailureResult.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalFailureResult;
+
+import java.util.List;
+
+public class DefaultFailureResult extends AbstractOperationResult implements InternalFailureResult {
+    private final List<DefaultFailure> failures;
+
+    public DefaultFailureResult(long startTime, long endTime, List<DefaultFailure> failures) {
+        super(startTime, endTime, "failed");
+        this.failures = failures;
+    }
+
+    @Override
+    public List<DefaultFailure> getFailures() {
+        return failures;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationDescriptor.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationDescriptor.java
new file mode 100644
index 0000000..2d0911b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationDescriptor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationDescriptor;
+
+import java.io.Serializable;
+
+public class DefaultOperationDescriptor implements Serializable, InternalOperationDescriptor {
+    private final Object id;
+    private final String name;
+    private final String displayName;
+    private final Object parentId;
+
+    public DefaultOperationDescriptor(Object id, String name, String displayName, Object parentId) {
+        this.id = id;
+        this.name = name;
+        this.displayName = displayName;
+        this.parentId = parentId;
+    }
+
+    @Override
+    public Object getId() {
+        return id;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    public Object getParentId() {
+        return parentId;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationFinishedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationFinishedProgressEvent.java
new file mode 100644
index 0000000..c67ea4d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationFinishedProgressEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationFinishedProgressEvent;
+
+public class DefaultOperationFinishedProgressEvent extends AbstractProgressEvent<DefaultOperationDescriptor> implements InternalOperationFinishedProgressEvent {
+    private final AbstractOperationResult result;
+
+    public DefaultOperationFinishedProgressEvent(long eventTime, DefaultOperationDescriptor descriptor, AbstractOperationResult result) {
+        super(eventTime, descriptor);
+        this.result = result;
+    }
+
+    @Override
+    public AbstractOperationResult getResult() {
+        return result;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s %s", getDescriptor().getDisplayName(), result.getOutcomeDescription());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationStartedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationStartedProgressEvent.java
new file mode 100644
index 0000000..a4ecbc2
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultOperationStartedProgressEvent.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationStartedProgressEvent;
+
+public class DefaultOperationStartedProgressEvent extends AbstractProgressEvent<DefaultOperationDescriptor> implements InternalOperationStartedProgressEvent {
+    public DefaultOperationStartedProgressEvent(long eventTime, DefaultOperationDescriptor descriptor) {
+        super(eventTime, descriptor);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s started", getDescriptor().getName());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultSuccessResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultSuccessResult.java
new file mode 100644
index 0000000..0e175d4
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultSuccessResult.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalSuccessResult;
+
+public class DefaultSuccessResult extends AbstractOperationResult implements InternalSuccessResult {
+    public DefaultSuccessResult(long startTime, long endTime) {
+        super(startTime, endTime, "succeeded");
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskDescriptor.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskDescriptor.java
new file mode 100644
index 0000000..99dc3a5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskDescriptor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalTaskDescriptor;
+
+import java.io.Serializable;
+
+public class DefaultTaskDescriptor implements Serializable, InternalTaskDescriptor {
+
+    private final Object id;
+    private final String displayName;
+    private final String taskPath;
+    private final Object parentId;
+
+    public DefaultTaskDescriptor(Object id, String taskPath, String displayName, Object parentId) {
+        this.id = id;
+        this.displayName = displayName;
+        this.taskPath = taskPath;
+        this.parentId = parentId;
+    }
+
+    @Override
+    public Object getId() {
+        return id;
+    }
+
+    @Override
+    public String getName() {
+        return taskPath;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    public String getTaskPath() {
+        return taskPath;
+    }
+
+    @Override
+    public Object getParentId() {
+        return parentId;
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFailureResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFailureResult.java
new file mode 100644
index 0000000..a81b2e4
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFailureResult.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalTaskFailureResult;
+
+import java.util.List;
+
+public class DefaultTaskFailureResult extends AbstractTaskResult implements InternalTaskFailureResult {
+    private final List<DefaultFailure> failures;
+
+    public DefaultTaskFailureResult(long startTime, long endTime, List<DefaultFailure> failures) {
+        super(startTime, endTime, "failed");
+        this.failures = failures;
+    }
+
+    @Override
+    public List<DefaultFailure> getFailures() {
+        return failures;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFinishedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFinishedProgressEvent.java
new file mode 100644
index 0000000..590d38a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskFinishedProgressEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationFinishedProgressEvent;
+
+public class DefaultTaskFinishedProgressEvent extends AbstractProgressEvent<DefaultTaskDescriptor> implements InternalOperationFinishedProgressEvent {
+    private final AbstractTaskResult result;
+
+    public DefaultTaskFinishedProgressEvent(long eventTime, DefaultTaskDescriptor descriptor, AbstractTaskResult result) {
+        super(eventTime, descriptor);
+        this.result = result;
+    }
+
+    @Override
+    public AbstractTaskResult getResult() {
+        return result;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s %s", getDescriptor().getDisplayName(), result.getOutcomeDescription());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSkippedResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSkippedResult.java
new file mode 100644
index 0000000..2e66ac7
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSkippedResult.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalTaskSkippedResult;
+
+public class DefaultTaskSkippedResult extends AbstractTaskResult implements InternalTaskSkippedResult {
+    private final String skipMessage;
+
+    public DefaultTaskSkippedResult(long startTime, long endTime, String skipMessage) {
+        super(startTime, endTime, "skipped");
+        this.skipMessage = skipMessage;
+    }
+
+    @Override
+    public String getSkipMessage() {
+        return skipMessage;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskStartedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskStartedProgressEvent.java
new file mode 100644
index 0000000..7fcd14e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskStartedProgressEvent.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalOperationStartedProgressEvent;
+
+public class DefaultTaskStartedProgressEvent extends AbstractProgressEvent<DefaultTaskDescriptor> implements InternalOperationStartedProgressEvent {
+    public DefaultTaskStartedProgressEvent(long eventTime, DefaultTaskDescriptor descriptor) {
+        super(eventTime, descriptor);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s started", getDescriptor().getDisplayName());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSuccessResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSuccessResult.java
new file mode 100644
index 0000000..710ba1e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTaskSuccessResult.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.events.InternalTaskSuccessResult;
+
+public class DefaultTaskSuccessResult extends AbstractTaskResult implements InternalTaskSuccessResult {
+    private final boolean upToDate;
+
+    public DefaultTaskSuccessResult(long startTime, long endTime, boolean upToDate) {
+        super(startTime, endTime, "succeeded");
+        this.upToDate = upToDate;
+    }
+
+    @Override
+    public boolean isUpToDate() {
+        return upToDate;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestDescriptor.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestDescriptor.java
index 3fea295..020a7b0 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestDescriptor.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestDescriptor.java
@@ -16,11 +16,12 @@
 
 package org.gradle.tooling.internal.provider.events;
 
+import org.gradle.tooling.internal.protocol.events.InternalOperationDescriptor;
 import org.gradle.tooling.internal.protocol.events.InternalJvmTestDescriptor;
 
 import java.io.Serializable;
 
-public class DefaultTestDescriptor implements Serializable, InternalJvmTestDescriptor {
+public class DefaultTestDescriptor implements Serializable, InternalJvmTestDescriptor, InternalOperationDescriptor {
 
     private final Object id;
     private final String name;
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFailureResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFailureResult.java
index 8c4accf..ca66f69 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFailureResult.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFailureResult.java
@@ -24,16 +24,11 @@ public class DefaultTestFailureResult extends AbstractTestResult implements Inte
     private final List<DefaultFailure> failures;
 
     public DefaultTestFailureResult(long startTime, long endTime, List<DefaultFailure> failures) {
-        super(startTime, endTime);
+        super(startTime, endTime, "failed");
         this.failures = failures;
     }
 
     @Override
-    public String getOutcomeDescription() {
-        return "failed";
-    }
-
-    @Override
     public List<DefaultFailure> getFailures() {
         return failures;
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFinishedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFinishedProgressEvent.java
index 2e90d75..a941a2e 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFinishedProgressEvent.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestFinishedProgressEvent.java
@@ -16,9 +16,10 @@
 
 package org.gradle.tooling.internal.provider.events;
 
+import org.gradle.tooling.internal.protocol.events.InternalOperationFinishedProgressEvent;
 import org.gradle.tooling.internal.protocol.events.InternalTestFinishedProgressEvent;
 
-public class DefaultTestFinishedProgressEvent extends AbstractTestProgressEvent implements InternalTestFinishedProgressEvent {
+public class DefaultTestFinishedProgressEvent extends AbstractProgressEvent<DefaultTestDescriptor> implements InternalTestFinishedProgressEvent, InternalOperationFinishedProgressEvent {
     private final AbstractTestResult result;
 
     public DefaultTestFinishedProgressEvent(long eventTime, DefaultTestDescriptor descriptor, AbstractTestResult result) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSkippedResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSkippedResult.java
index 29bec84..5b99515 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSkippedResult.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSkippedResult.java
@@ -20,11 +20,6 @@ import org.gradle.tooling.internal.protocol.events.InternalTestSkippedResult;
 
 public class DefaultTestSkippedResult extends AbstractTestResult implements InternalTestSkippedResult {
     public DefaultTestSkippedResult(long startTime, long endTime) {
-        super(startTime, endTime);
-    }
-
-    @Override
-    public String getOutcomeDescription() {
-        return "skipped";
+        super(startTime, endTime, "skipped");
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestStartedProgressEvent.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestStartedProgressEvent.java
index 23add28..781cc01 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestStartedProgressEvent.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestStartedProgressEvent.java
@@ -16,9 +16,10 @@
 
 package org.gradle.tooling.internal.provider.events;
 
+import org.gradle.tooling.internal.protocol.events.InternalOperationStartedProgressEvent;
 import org.gradle.tooling.internal.protocol.events.InternalTestStartedProgressEvent;
 
-public class DefaultTestStartedProgressEvent extends AbstractTestProgressEvent implements InternalTestStartedProgressEvent {
+public class DefaultTestStartedProgressEvent extends AbstractProgressEvent<DefaultTestDescriptor> implements InternalTestStartedProgressEvent, InternalOperationStartedProgressEvent {
     public DefaultTestStartedProgressEvent(long eventTime, DefaultTestDescriptor descriptor) {
         super(eventTime, descriptor);
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSuccessResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSuccessResult.java
index fe56ba5..db657cb 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSuccessResult.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/events/DefaultTestSuccessResult.java
@@ -20,11 +20,6 @@ import org.gradle.tooling.internal.protocol.events.InternalTestSuccessResult;
 
 public class DefaultTestSuccessResult extends AbstractTestResult implements InternalTestSuccessResult {
     public DefaultTestSuccessResult(long startTime, long endTime) {
-        super(startTime, endTime);
-    }
-
-    @Override
-    public String getOutcomeDescription() {
-        return "succeeded";
+        super(startTime, endTime, "succeeded");
     }
 }
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 f853608..0c917de 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
@@ -49,13 +49,14 @@ class BuildActionsFactoryTest extends Specification {
     ServiceRegistry loggingServices = Mock()
     PropertiesToDaemonParametersConverter propertiesToDaemonParametersConverter = Stub()
     PropertiesToStartParameterConverter propertiesToStartParameterConverter = Stub()
-
-    BuildActionsFactory factory = new BuildActionsFactory(
-            loggingServices, new DefaultCommandLineConverter(), new DaemonCommandLineConverter(),
+    ParametersConverter parametersConverter = new ParametersConverter(
             Stub(LayoutCommandLineConverter), Stub(SystemPropertiesCommandLineConverter),
             Stub(LayoutToPropertiesConverter), propertiesToStartParameterConverter,
+            new DefaultCommandLineConverter(), new DaemonCommandLineConverter(),
             propertiesToDaemonParametersConverter)
 
+    BuildActionsFactory factory = new BuildActionsFactory(loggingServices, parametersConverter)
+
     def setup() {
         _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
         _ * loggingServices.get(ProgressLoggerFactory) >> Mock(ProgressLoggerFactory)
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy
index 86730b2..7652ce4 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.launcher.cli
 import org.gradle.api.Action
 import spock.lang.Specification
 import org.gradle.launcher.bootstrap.ExecutionListener
-import org.gradle.launcher.exec.ReportedException
+import org.gradle.initialization.ReportedException
 
 class ExceptionReportingActionTest extends Specification {
     final Action<ExecutionListener> target = Mock()
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 af2847e..3f2a929 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
@@ -19,7 +19,7 @@ import org.gradle.StartParameter
 import org.gradle.api.logging.LogLevel
 import org.gradle.initialization.BuildClientMetaData
 import org.gradle.initialization.BuildRequestContext
-import org.gradle.initialization.FixedBuildCancellationToken
+import org.gradle.initialization.DefaultBuildCancellationToken
 import org.gradle.launcher.exec.BuildActionExecuter
 import org.gradle.launcher.exec.BuildActionParameters
 import spock.lang.Specification
@@ -42,7 +42,7 @@ class RunBuildActionTest extends Specification {
         startParameter.logLevel >> LogLevel.ERROR
         1 * client.execute({!null}, {!null}, {!null}) >> { ExecuteBuildAction action, BuildRequestContext context, BuildActionParameters build ->
             assert action.startParameter == startParameter
-            assert context.cancellationToken instanceof FixedBuildCancellationToken
+            assert context.cancellationToken instanceof DefaultBuildCancellationToken
             assert context.client == clientMetaData
             assert context.buildTimeClock.startTime == startTime
             assert build == parameters
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 3ad3084..a9c48dc 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
@@ -13,51 +13,65 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.launcher.daemon.bootstrap
 
-import spock.lang.Specification
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 
-class DaemonOutputConsumerTest extends Specification {
-
-    def consumer = new DaemonOutputConsumer()
+class DaemonOutputConsumerTest extends ConcurrentSpec {
+    def consumer = new DaemonOutputConsumer(new ByteArrayInputStream([] as byte[]))
 
     def "input process and name cannot be null"() {
         when:
-        consumer.connectStreams((Process) null, "foo")
+        consumer.connectStreams((Process) null, "foo", executorFactory)
         then:
         thrown(IllegalArgumentException)
 
         when:
-        consumer.connectStreams(Mock(Process), null)
+        consumer.connectStreams(Mock(Process), null, executorFactory)
         then:
         thrown(IllegalArgumentException)
     }
 
-    def "consumes input until EOF"() {
+    def "forwards process input"() {
+        def consumer = new DaemonOutputConsumer(new ByteArrayInputStream("send this to the process".bytes))
+        def receivedInput = new ByteArrayOutputStream()
+        def process = process("", receivedInput)
+
         when:
-        consumer.connectStreams(new ByteArrayInputStream('hey Joe!'.bytes) , "cool process")
+        consumer.connectStreams(process , "cool process", executorFactory)
         consumer.start()
         consumer.stop()
+
         then:
-        consumer.processOutput.trim() == 'hey Joe!'
+        receivedInput.toString() == "send this to the process"
     }
 
-    def "consumes input greeting noticed in output"() {
-        given:
-        consumer.startupCommunication = Mock(DaemonStartupCommunication)
-        consumer.startupCommunication.containsGreeting( {it.contains "Come visit Krakow"} ) >> true
+    def "consumes process output until EOF"() {
+        def process = process('hey Joe!')
 
         when:
-        def ouptut = """
+        consumer.connectStreams(process , "cool process", executorFactory)
+        consumer.start()
+        consumer.stop()
+        then:
+        consumer.processOutput.trim() == 'hey Joe!'
+    }
+
+    def "consumes process greeting noticed in output"() {
+        def output = """
            Hey!
            Come visit Krakow
            It's nice
            !!!
         """
-        consumer.connectStreams(new ByteArrayInputStream(ouptut.toString().bytes) , "cool process")
+        def process = process(output)
+
+        given:
+        consumer.startupCommunication = Mock(DaemonStartupCommunication)
+        consumer.startupCommunication.containsGreeting( {it.contains "Come visit Krakow"} ) >> true
+
+        when:
+        consumer.connectStreams(process , "cool process", executorFactory)
         consumer.start()
         consumer.stop()
 
@@ -74,7 +88,7 @@ class DaemonOutputConsumerTest extends Specification {
 
     def "starting is required"() {
         when:
-        consumer.connectStreams(new ByteArrayInputStream(new byte[0]) , "cool process")
+        consumer.connectStreams(process(""), "cool process", executorFactory)
 
         then:
         illegalStateReportedWhen {consumer.stop()}
@@ -83,7 +97,7 @@ class DaemonOutputConsumerTest extends Specification {
 
     def "stopping is required"() {
         when:
-        consumer.connectStreams(new ByteArrayInputStream(new byte[0]) , "cool process")
+        consumer.connectStreams(process("") , "cool process", executorFactory)
         consumer.start()
 
         then:
@@ -96,4 +110,11 @@ class DaemonOutputConsumerTest extends Specification {
             assert false
         } catch (IllegalStateException) {}
     }
+
+    def process(String input = "", OutputStream processInput = new ByteArrayOutputStream()) {
+        return Stub(Process) {
+            getInputStream() >> new ByteArrayInputStream(input.bytes)
+            getOutputStream() >> processInput
+        }
+    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonCancelForwarderTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonCancelForwarderTest.groovy
index a0bf318..7c7ec55 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonCancelForwarderTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonCancelForwarderTest.groovy
@@ -52,7 +52,7 @@ class DaemonCancelForwarderTest extends ConcurrentSpecification {
 
     def "cancel is forwarded when received before stop"() {
         when:
-        cancellationToken.doCancel()
+        cancellationToken.cancel()
         forwarder.stop()
 
         then:
@@ -62,9 +62,9 @@ class DaemonCancelForwarderTest extends ConcurrentSpecification {
     def "cancel is ignored after stop"() {
         when:
         forwarder.stop()
-        cancellationToken.doCancel()
+        cancellationToken.cancel()
 
         then:
         0 * dispatch._
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
index a03560c..1f7d103 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
@@ -31,8 +31,8 @@ import org.gradle.launcher.daemon.server.api.DaemonCommandAction
 import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.ForwardClientInput
+import org.gradle.launcher.exec.BuildExecuter
 import org.gradle.launcher.exec.DefaultBuildActionParameters
-import org.gradle.launcher.exec.InProcessBuildActionExecuter
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.messaging.remote.internal.MessageIOException
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -48,7 +48,7 @@ class DaemonServerExceptionHandlingTest extends Specification {
     def buildRequestContext = Stub(BuildRequestContext) {
         getClient() >> new GradleLauncherMetaData()
     }
-    def parameters = new DefaultBuildActionParameters(new HashMap(System.properties), [:], temp.testDirectory, LogLevel.ERROR, IMPLICITLY_DISABLED)
+    def parameters = new DefaultBuildActionParameters(new HashMap(System.properties), [:], temp.testDirectory, LogLevel.ERROR, IMPLICITLY_DISABLED, false, false)
 
     static class DummyLauncherAction implements BuildAction, Serializable {
         StartParameter startParameter
@@ -80,7 +80,7 @@ class DaemonServerExceptionHandlingTest extends Specification {
         //we need to override some methods to inject a failure action into the sequence
         def services = new EmbeddedDaemonClientServices() {
             DaemonCommandExecuter createDaemonCommandExecuter() {
-                return new DefaultDaemonCommandExecuter(get(InProcessBuildActionExecuter),
+                return new DefaultDaemonCommandExecuter(get(BuildExecuter),
                         get(ProcessEnvironment), getFactory(LoggingManagerInternal.class).create(),
                         new File("dummy"), new StubDaemonHealthServices()) {
                     List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ContinuousBuildActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ContinuousBuildActionExecuterTest.groovy
new file mode 100644
index 0000000..5e4f10f
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ContinuousBuildActionExecuterTest.groovy
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.exec
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.execution.internal.TaskInputsListener
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.initialization.BuildRequestMetaData
+import org.gradle.initialization.DefaultBuildCancellationToken
+import org.gradle.initialization.DefaultBuildRequestContext
+import org.gradle.initialization.NoOpBuildEventConsumer
+import org.gradle.initialization.ReportedException
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import org.gradle.internal.event.DefaultListenerManager
+import org.gradle.internal.filewatch.FileSystemChangeWaiter
+import org.gradle.internal.invocation.BuildAction
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.logging.TestStyledTextOutputFactory
+import org.gradle.util.Clock
+import org.gradle.util.RedirectStdIn
+import org.junit.Rule
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+class ContinuousBuildActionExecuterTest extends Specification {
+
+    @Rule
+    RedirectStdIn redirectStdIn = new RedirectStdIn()
+
+    def delegate = Mock(BuildActionExecuter)
+    def action = Mock(BuildAction)
+    def cancellationToken = new DefaultBuildCancellationToken()
+    def clock = Mock(Clock)
+    def requestMetadata = Stub(BuildRequestMetaData)
+    def requestContext = new DefaultBuildRequestContext(requestMetadata, cancellationToken, new NoOpBuildEventConsumer())
+    def actionParameters = Stub(BuildActionParameters)
+    def waiter = Mock(FileSystemChangeWaiter)
+    def listenerManager = new DefaultListenerManager()
+    @AutoCleanup("stop")
+    def executorFactory = new DefaultExecutorFactory()
+    def executer = executer()
+
+    private File file = new File('file')
+
+    def setup() {
+        requestMetadata.getBuildTimeClock() >> clock
+    }
+
+    def "uses underlying executer when continuous build is not enabled"() {
+        when:
+        singleBuild()
+        executeBuild()
+
+        then:
+        1 * delegate.execute(action, requestContext, actionParameters)
+        0 * waiter._
+    }
+
+    def "allows exceptions to propagate for single builds"() {
+        when:
+        singleBuild()
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            throw new RuntimeException("!")
+        }
+        executeBuild()
+
+        then:
+        thrown(RuntimeException)
+    }
+
+    def "waits for waiter"() {
+        when:
+        continuousBuild()
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            declareInput(file)
+        }
+        executeBuild()
+
+        then:
+        1 * waiter.wait(_, _, _) >> {
+            cancellationToken.cancel()
+        }
+    }
+
+    def "exits if there are no file system inputs"() {
+        when:
+        continuousBuild()
+        1 * delegate.execute(action, requestContext, actionParameters)
+        executeBuild()
+
+        then:
+        0 * waiter.wait(_, _, _)
+    }
+
+    def "throws exception if last build fails in continous mode"() {
+        when:
+        continuousBuild()
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            declareInput(file)
+            throw new ReportedException(new Exception("!"))
+        }
+        executeBuild()
+
+        then:
+        1 * waiter.wait(_, _, _) >> {
+            cancellationToken.cancel()
+        }
+        thrown(ReportedException)
+    }
+
+    def "keeps running after failures when continuous"() {
+        when:
+        continuousBuild()
+        executeBuild()
+
+        then:
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            declareInput(file)
+        }
+
+        and:
+        1 * waiter.wait(_, _, _)
+
+        and:
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            declareInput(file)
+            throw new ReportedException(new Exception("!"))
+        }
+
+        and:
+        1 * waiter.wait(_, _, _)
+
+        and:
+        1 * delegate.execute(action, requestContext, actionParameters) >> {
+            declareInput(file)
+        }
+
+        and:
+        1 * waiter.wait(_, _, _) >> {
+            cancellationToken.cancel()
+        }
+    }
+
+    def "doesn't prevent use on java 6 when not using continuous"() {
+        given:
+        executer = executer(JavaVersion.VERSION_1_6)
+
+        when:
+        singleBuild()
+
+        and:
+        executeBuild()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "prevents use on java 6 when using continuous"() {
+        given:
+        executer = executer(JavaVersion.VERSION_1_6)
+
+        when:
+        continuousBuild()
+
+        and:
+        executeBuild()
+
+        then:
+        def e = thrown IllegalStateException
+        e.message == "Continuous build requires Java 7 or later."
+    }
+
+    def "can use on all versions later than 7"() {
+        given:
+        executer = executer(javaVersion)
+
+        when:
+        continuousBuild()
+
+        and:
+        executeBuild()
+
+        then:
+        noExceptionThrown()
+
+        where:
+        javaVersion << JavaVersion.values().findAll { it >= JavaVersion.VERSION_1_7 }
+    }
+
+    private void singleBuild() {
+        actionParameters.continuous >> false
+    }
+
+    private void continuousBuild() {
+        actionParameters.continuous >> true
+    }
+
+    private void executeBuild() {
+        executer.execute(action, requestContext, actionParameters)
+    }
+
+    private void declareInput(File file) {
+        listenerManager.getBroadcaster(TaskInputsListener).onExecute(Mock(TaskInternal), new SimpleFileCollection(file))
+    }
+
+    private ContinuousBuildActionExecuter executer(JavaVersion javaVersion = JavaVersion.VERSION_1_7) {
+        new ContinuousBuildActionExecuter(delegate, listenerManager, new TestStyledTextOutputFactory(), javaVersion, OperatingSystem.current(), executorFactory, waiter)
+    }
+
+}
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 5a93196..78cb6f6 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
@@ -25,7 +25,7 @@ public class DefaultBuildActionParametersTest extends Specification {
 
     def "is serializable"() {
         given:
-        def params = new DefaultBuildActionParameters(System.properties, System.getenv(), new File("."), LogLevel.ERROR, IMPLICITLY_DISABLED)
+        def params = new DefaultBuildActionParameters(System.properties, System.getenv(), new File("."), LogLevel.ERROR, IMPLICITLY_DISABLED, false, true)
         ObjectOutputStream out = new ObjectOutputStream(new ByteArrayOutputStream());
 
         when:
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
index 60ab42b..7367b2b 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessBuildActionExecuterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessBuildActionExecuterTest.groovy
@@ -98,7 +98,6 @@ class InProcessBuildActionExecuterTest extends Specification {
         and:
         1 * factory.newInstance(startParameter, buildRequestContext) >> launcher
         1 * launcher.run() >> buildResult
-        _ * buildResult.failure >> null
         _ * buildResult.gradle >> gradle
         _ * actionRunner.run(action, !null) >> { BuildAction a, BuildController controller ->
             assert controller.run() == gradle
@@ -120,7 +119,6 @@ class InProcessBuildActionExecuterTest extends Specification {
         and:
         1 * factory.newInstance(startParameter, buildRequestContext) >> launcher
         1 * launcher.getBuildAnalysis() >> buildResult
-        _ * buildResult.failure >> null
         _ * buildResult.gradle >> gradle
         _ * actionRunner.run(action, !null) >> { BuildAction a, BuildController controller ->
             assert controller.configure() == gradle
@@ -149,23 +147,61 @@ class InProcessBuildActionExecuterTest extends Specification {
         1 * launcher.stop()
     }
 
-    def "wraps build failure and cleans up"() {
+    def "forwards build failure and cleans up"() {
         def failure = new RuntimeException()
 
-        given:
-        buildResult.failure >> failure
+        when:
+        executer.execute(action, buildRequestContext, param)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        1 * factory.newInstance(startParameter, buildRequestContext) >> launcher
+        1 * launcher.run() >> { throw failure }
+        _ * actionRunner.run(action, !null) >> { BuildAction action, BuildController controller ->
+            controller.run()
+        }
+        1 * launcher.stop()
+    }
+
+    def "forwards configure failure and cleans up"() {
+        def failure = new RuntimeException()
 
         when:
         executer.execute(action, buildRequestContext, param)
 
         then:
-        ReportedException e = thrown()
-        e.cause == failure
+        RuntimeException e = thrown()
+        e == failure
 
         and:
         1 * factory.newInstance(startParameter, buildRequestContext) >> launcher
-        1 * launcher.run() >> buildResult
-        _ * actionRunner.run(action, !null) >> { BuildAction a, BuildController controller ->
+        1 * launcher.buildAnalysis >> { throw failure }
+        _ * actionRunner.run(action, !null) >> { BuildAction action, BuildController controller ->
+            controller.configure()
+        }
+        1 * launcher.stop()
+    }
+
+    def "cannot run after configuration failure"() {
+        when:
+        executer.execute(action, buildRequestContext, param)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot use launcher after build has completed.'
+
+        and:
+        1 * factory.newInstance(startParameter, buildRequestContext) >> launcher
+        1 * launcher.buildAnalysis >> { throw new RuntimeException() }
+        _ * actionRunner.run(action, !null) >> { BuildAction action, BuildController controller ->
+            try {
+                controller.configure()
+            } catch (RuntimeException) {
+                // Ignore
+            }
             controller.run()
         }
         1 * launcher.stop()
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
index 7c3f5df..3c14ac8 100644
--- 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
@@ -18,6 +18,7 @@ package org.gradle.tooling.internal.provider
 
 import org.gradle.internal.classloader.MutableURLClassLoader
 import org.gradle.tooling.BuildAction
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.TestClassLoader
 import spock.lang.Issue
 
@@ -68,6 +69,7 @@ class ClasspathInfererTest extends AbstractClassGraphSpec {
     }
 
     @Issue("GRADLE-3245")
+    @LeaksFileHandles
     def "determines action and tooling API classpath when loaded from a jar via a non-standard ClassLoader"() {
         def cl = new NetBeansLikeClassLoader(ClassLoader.systemClassLoader.parent, [isolatedClassesInJar(CustomAction, CustomModel)] + toolingApiClassPath)
         def actionClass = cl.loadClass(CustomAction.name)
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
index 90e93a7..9d72737 100644
--- 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
@@ -15,11 +15,11 @@
  */
 package org.gradle.tooling.internal.provider
 
-import org.gradle.internal.invocation.BuildAction
 import org.gradle.initialization.BuildRequestContext
+import org.gradle.internal.invocation.BuildAction
 import org.gradle.launcher.daemon.client.DaemonClient
 import org.gradle.launcher.daemon.configuration.DaemonParameters
-import org.gradle.launcher.exec.ReportedException
+import org.gradle.initialization.ReportedException
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1
 import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters
 import spock.lang.Specification
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/AbstractDaemonFixture.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/AbstractDaemonFixture.groovy
deleted file mode 100644
index 948c23c..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/AbstractDaemonFixture.groovy
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.testing
-
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.launcher.daemon.context.DaemonContext
-import org.gradle.process.internal.ExecHandleBuilder
-
-abstract class AbstractDaemonFixture implements DaemonFixture {
-    public static final int STATE_CHANGE_TIMEOUT = 20000
-    final DaemonContext context
-
-    AbstractDaemonFixture(File daemonLog) {
-        this.context = DaemonContextParser.parseFrom(daemonLog.text)
-        if (this.context.pid == null) {
-            println "PID in daemon log ($daemonLog.absolutePath) is null."
-            println "daemon.log exists: ${daemonLog.exists()}"
-
-            println "start daemon.log content: "
-            println "{daemonLog.text.isEmpty()}) = ${daemonLog.text.isEmpty()})"
-            println daemonLog.text;
-            println "end daemon.log content"
-
-        }
-    }
-
-    void becomesIdle() {
-        waitForState(State.idle)
-    }
-
-    void stops() {
-        waitForState(State.stopped)
-    }
-
-    @Override
-    void assertIdle() {
-        assertHasState(State.idle)
-    }
-
-    @Override
-    void assertBusy() {
-        assertHasState(State.busy)
-    }
-
-    @Override
-    void assertStopped() {
-        assertHasState(State.stopped)
-    }
-
-    protected abstract void waitForState(State state)
-
-    protected abstract void assertHasState(State state)
-
-    /**
-     * Forcefully kills this daemon.
-     */
-    void kill() {
-        println "Killing daemon with pid: $context.pid"
-        def output = new ByteArrayOutputStream()
-        def e = new ExecHandleBuilder()
-                .commandLine(killArgs(context.pid))
-                .redirectErrorStream()
-                .setStandardOutput(output)
-                .workingDir(new File(".").absoluteFile) //does not matter
-                .build()
-        e.start()
-        def result = e.waitForFinish()
-        result.rethrowFailure()
-    }
-
-    private static Object[] killArgs(Long pid) {
-        if (pid == null) {
-            throw new RuntimeException("Unable to force kill the daemon because provided pid is null!")
-        }
-        if (OperatingSystem.current().unix) {
-            return ["kill", "-9", pid]
-        } else if (OperatingSystem.current().windows) {
-            return ["taskkill.exe", "/F", "/T", "/PID", pid]
-        } else {
-            throw new RuntimeException("This implementation does not know how to forcefully kill the daemon on os: " + OperatingSystem.current())
-        }
-    }
-
-    @SuppressWarnings("FieldName")
-    enum State {
-        busy, idle, stopped
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java
deleted file mode 100644
index b86d763..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.launcher.daemon.testing;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import org.gradle.launcher.daemon.context.DaemonContext;
-import org.gradle.launcher.daemon.context.DefaultDaemonContext;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DaemonContextParser {
-    public static DaemonContext parseFromFile(File file) {
-        try {
-            BufferedReader reader = new BufferedReader(new FileReader(file));
-            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
-                DaemonContext context = parseFrom(line);
-                if (context != null) {
-                    return context;
-                }
-            }
-        } catch(FileNotFoundException e) {
-            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].", e);
-        } catch (IOException e) {
-            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].", e);
-        }
-        throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + file.getAbsolutePath() + "].");
-    }
-
-    public static DaemonContext parseFromString(String source) {
-        DaemonContext context = parseFrom(source);
-        if (context == null) {
-            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + source + "].");
-        }
-        return context;
-    }
-
-    private static DaemonContext parseFrom(String source) {
-        Pattern pattern = Pattern.compile("^.*DefaultDaemonContext\\[(uid=[^\\n,]+)?,?javaHome=([^\\n]+),daemonRegistryDir=([^\\n]+),pid=([^\\n]+),idleTimeout=(.+?),daemonOpts=([^\\n]+)].*",
-                Pattern.MULTILINE + Pattern.DOTALL);
-        Matcher matcher = pattern.matcher(source);
-
-        if (matcher.matches()) {
-            String uid = matcher.group(1) == null ? null : matcher.group(1).substring("uid=".length());
-            String javaHome = matcher.group(2);
-            String daemonRegistryDir = matcher.group(3);
-            String pidStr = matcher.group(4);
-            Long pid = pidStr.equals("null") ? null : Long.parseLong(pidStr);
-            Integer idleTimeout = Integer.decode(matcher.group(5));
-            List<String> jvmOpts = Lists.newArrayList(Splitter.on(',').split(matcher.group(6)));
-            return new DefaultDaemonContext(uid, new File(javaHome), new File(daemonRegistryDir), pid, idleTimeout, jvmOpts);
-        } else {
-            return null;
-        }
-    }
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonFixture.java b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonFixture.java
deleted file mode 100644
index c2f2c4d..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonFixture.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.testing;
-
-public interface DaemonFixture {
-    /**
-     * Returns the TCP port used by this daemon.
-     */
-    int getPort();
-
-    /**
-     * Forcefully kills this daemon.
-     */
-    void kill();
-
-    /**
-     * Asserts that this daemon becomes idle within a short timeout. Blocks until this has happened.
-     */
-    void becomesIdle();
-
-    /**
-     * Asserts that this daemon stops and is no longer visible to any clients within a short timeout. Blocks until this has happened.
-     */
-    void stops();
-
-    /**
-     * Asserts that this daemon is currently idle.
-     */
-    void assertIdle();
-
-    /**
-     * Asserts that this daemon is currently busy.
-     */
-    void assertBusy();
-
-    /**
-     * Asserts that this daemon has stopped and is no longer visible to any clients.
-     */
-    void assertStopped();
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogFileStateProbe.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogFileStateProbe.groovy
deleted file mode 100644
index 757c367..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogFileStateProbe.groovy
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.testing
-
-import org.gradle.launcher.daemon.context.DaemonContext
-import org.gradle.launcher.daemon.logging.DaemonMessages
-import org.gradle.launcher.daemon.testing.AbstractDaemonFixture.State
-
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-class DaemonLogFileStateProbe implements DaemonStateProbe {
-    private final DaemonContext context
-    private final File log
-    private final String startBuildMessage
-    private final String finishBuildMessage
-
-    DaemonLogFileStateProbe(File daemonLog, DaemonContext context, String startBuildMessage = DaemonMessages.STARTED_BUILD, String finishBuildMessage = DaemonMessages.FINISHED_BUILD) {
-        this.finishBuildMessage = finishBuildMessage
-        this.startBuildMessage = startBuildMessage
-        this.log = daemonLog
-        this.context = context
-    }
-
-    @Override
-    String toString() {
-        return "DaemonLogFile{file: ${log}, context: ${context}}"
-    }
-
-    DaemonContext getContext() {
-        return context
-    }
-
-    State getCurrentState() {
-        getStates().last()
-    }
-
-    List<State> getStates() {
-        def states = new LinkedList<State>()
-        states << State.idle
-        log.eachLine {
-            if (it.contains(startBuildMessage)) {
-                states << State.busy
-            } else if (it.contains(finishBuildMessage)) {
-                states << State.idle
-            } else if (it.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)) {
-                states << State.stopped
-            }
-        }
-        states
-    }
-
-    String getLog() {
-        return log.text
-    }
-
-    int getPort() {
-        Pattern pattern = Pattern.compile("^.*" + DaemonMessages.ADVERTISING_DAEMON + ".*port:(\\d+).*",
-                Pattern.MULTILINE + Pattern.DOTALL);
-
-        Matcher matcher = pattern.matcher(log.text);
-        assert matcher.matches(): "Unable to find daemon address in the daemon log. Daemon: $context"
-
-        try {
-            return Integer.parseInt(matcher.group(1))
-        } catch (NumberFormatException e) {
-            throw new RuntimeException("Unexpected format of the port number found in the daemon log. Daemon: $context")
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.groovy
deleted file mode 100644
index 5b163cd..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.groovy
+++ /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.launcher.daemon.testing
-
-import org.gradle.internal.service.ServiceRegistryBuilder
-import org.gradle.internal.service.scopes.GlobalScopeServices
-import org.gradle.launcher.daemon.client.DaemonClientGlobalServices
-import org.gradle.launcher.daemon.registry.DaemonRegistry
-import org.gradle.launcher.daemon.registry.DaemonRegistryServices
-import org.gradle.logging.LoggingServiceRegistry
-import org.gradle.testfixtures.internal.NativeServicesTestFixture
-import org.gradle.util.GradleVersion
-
-class DaemonLogsAnalyzer implements DaemonsFixture {
-    private final File daemonLogsDir
-    private final File daemonBaseDir
-    private final DaemonRegistry registry
-    private final String version
-
-    DaemonLogsAnalyzer(File daemonBaseDir, String version = GradleVersion.current().version) {
-        this.version = version
-        this.daemonBaseDir = daemonBaseDir
-        daemonLogsDir = new File(daemonBaseDir, version)
-        def services = ServiceRegistryBuilder.builder()
-                .parent(LoggingServiceRegistry.newEmbeddableLogging())
-                .parent(NativeServicesTestFixture.getInstance())
-                .provider(new GlobalScopeServices(false))
-                .provider(new DaemonClientGlobalServices())
-                .provider(new DaemonRegistryServices(daemonBaseDir))
-                .build()
-        registry = services.get(DaemonRegistry)
-    }
-
-    static DaemonsFixture newAnalyzer(File daemonBaseDir, String version = GradleVersion.current().version) {
-        return new DaemonLogsAnalyzer(daemonBaseDir, version)
-    }
-
-    DaemonRegistry getRegistry() {
-        return registry
-    }
-
-    void killAll() {
-        daemons*.kill()
-    }
-
-    List<DaemonFixture> getDaemons() {
-        assert daemonLogsDir.isDirectory()
-        return daemonLogsDir.listFiles().findAll { it.name.endsWith('.log') }.collect { daemonForLogFile(it) }
-    }
-
-    List<DaemonFixture> getVisible() {
-        return registry.all.collect { daemonForLogFile(new File(daemonLogsDir, "daemon-${it.pid}.out.log")) }
-    }
-
-    DaemonFixture daemonForLogFile(File logFile) {
-        if (version == GradleVersion.current().version) {
-            return new TestableDaemon(logFile, registry)
-        }
-        return new LegacyDaemon(logFile, version)
-    }
-
-    DaemonFixture getDaemon() {
-        def daemons = getDaemons()
-        assert daemons.size() == 1
-        daemons[0]
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonRegistryStateProbe.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonRegistryStateProbe.groovy
deleted file mode 100644
index 9c6acda..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonRegistryStateProbe.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.testing
-
-import org.gradle.launcher.daemon.context.DaemonContext
-import org.gradle.launcher.daemon.registry.DaemonRegistry
-import org.gradle.launcher.daemon.testing.AbstractDaemonFixture.State
-
-class DaemonRegistryStateProbe implements DaemonStateProbe {
-    private final DaemonRegistry registry
-    private final DaemonContext context
-
-    DaemonRegistryStateProbe(DaemonRegistry registry, DaemonContext context) {
-        this.context = context
-        this.registry = registry
-    }
-
-    @Override
-    State getCurrentState() {
-        def daemonInfo = registry.all.find { it.context.pid == context.pid }
-        if (daemonInfo == null) {
-            return State.stopped
-        }
-        return daemonInfo.idle ? State.idle : State.busy
-    }
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonStateProbe.java b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonStateProbe.java
deleted file mode 100644
index f84957f..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonStateProbe.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.testing;
-
-public interface DaemonStateProbe {
-    TestableDaemon.State getCurrentState();
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonsFixture.java b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonsFixture.java
deleted file mode 100644
index be9a90a..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/DaemonsFixture.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.testing;
-
-import java.util.List;
-
-public interface DaemonsFixture {
-    /**
-     * Kills all daemons.
-     */
-    void killAll();
-
-    /**
-     * Returns all known daemons. Includes any daemons that are no longer running.
-     */
-    List<? extends DaemonFixture> getDaemons();
-
-    /**
-     * Returns all daemons that are visible to clients. May include daemons that are no longer running (eg they have crashed).
-     */
-    List<? extends DaemonFixture> getVisible();
-
-    /**
-     * Convenience to get a single daemon. Fails if there is not exactly 1 daemon.
-     */
-    DaemonFixture getDaemon();
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/LegacyDaemon.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/LegacyDaemon.groovy
deleted file mode 100644
index d8d3bbc..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/LegacyDaemon.groovy
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.testing
-
-import org.gradle.launcher.daemon.testing.AbstractDaemonFixture.State
-import org.gradle.util.GradleVersion
-
-class LegacyDaemon extends AbstractDaemonFixture {
-    private final DaemonLogFileStateProbe logFileProbe
-
-    LegacyDaemon(File daemonLog, String version) {
-        super(daemonLog)
-        if (GradleVersion.version(version).baseVersion >= GradleVersion.version("2.2")) {
-            logFileProbe = new DaemonLogFileStateProbe(daemonLog, context)
-        } else {
-            logFileProbe = new DaemonLogFileStateProbe(daemonLog, context, "Daemon is busy, sleeping until state changes", "Daemon is idle, sleeping until state change")
-        }
-    }
-
-    protected void waitForState(State state) {
-        def expiry = System.currentTimeMillis() + STATE_CHANGE_TIMEOUT
-        def lastLogState = logFileProbe.currentState
-        while (expiry > System.currentTimeMillis() && lastLogState != state) {
-            Thread.sleep(200)
-            lastLogState = logFileProbe.currentState
-        }
-        if (lastLogState == state) {
-            return
-        }
-        throw new AssertionError("""Timeout waiting for daemon with pid ${context.pid} to reach state ${state}.
-Current state is ${lastLogState}.""")
-    }
-
-    @Override
-    protected void assertHasState(State state) {
-        assert logFileProbe.currentState == state
-    }
-
-    @Override
-    int getPort() {
-        throw new UnsupportedOperationException()
-    }
-}
diff --git a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.groovy b/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.groovy
deleted file mode 100644
index 164e589..0000000
--- a/subprojects/launcher/src/testFixtures/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.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.launcher.daemon.testing
-
-import org.gradle.launcher.daemon.registry.DaemonRegistry
-import org.gradle.launcher.daemon.testing.AbstractDaemonFixture.State
-
-class TestableDaemon extends AbstractDaemonFixture {
-    private final DaemonLogFileStateProbe logFileProbe
-    private final DaemonRegistryStateProbe registryProbe
-
-    TestableDaemon(File daemonLog, DaemonRegistry registry) {
-        super(daemonLog)
-        this.logFileProbe = new DaemonLogFileStateProbe(daemonLog, context)
-        this.registryProbe = new DaemonRegistryStateProbe(registry, context)
-    }
-
-    protected void waitForState(State state) {
-        def expiry = System.currentTimeMillis() + STATE_CHANGE_TIMEOUT
-        def lastRegistryState = registryProbe.currentState
-        def lastLogState = logFileProbe.currentState
-        while (expiry > System.currentTimeMillis() && (lastRegistryState != state || lastLogState != state)) {
-            Thread.sleep(200)
-            lastRegistryState = registryProbe.currentState
-            lastLogState = logFileProbe.currentState
-        }
-        if (lastRegistryState == state && lastLogState == state) {
-            return
-        }
-        throw new AssertionError("""Timeout waiting for daemon with pid ${context.pid} to reach state ${state}.
-Current registry state is ${lastRegistryState} and current log state is ${lastLogState}.""")
-    }
-
-    @Override
-    protected void assertHasState(State state) {
-        assert logFileProbe.currentState == state
-        assert registryProbe.currentState == state
-    }
-
-    String getLog() {
-        return logFileProbe.log
-    }
-
-    int getPort() {
-        return logFileProbe.port
-    }
-}
\ No newline at end of file
diff --git a/subprojects/maven/maven.gradle b/subprojects/maven/maven.gradle
index 3132f63..ddfb2d7 100644
--- a/subprojects/maven/maven.gradle
+++ b/subprojects/maven/maven.gradle
@@ -23,7 +23,7 @@ dependencies {
     compile project(':publish')
     compile libraries.slf4j_api
 
-    compile libraries.maven_publish
+    compile libraries.maven3
     compile "org.sonatype.pmaven:pmaven-common:0.8-20100325 at jar"
     compile "org.sonatype.pmaven:pmaven-groovy:0.8-20100325 at jar"
     compile "org.codehaus.plexus:plexus-component-annotations:1.5.2 at jar"
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 299e070..07b19be 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
@@ -201,6 +201,9 @@ class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
 
         and:
         resolveArtifacts(module) == ["snapshotPublish-${module.publishArtifactVersion}.jar"]
+
+        and:
+        module.parsedPom.version == '1.0-SNAPSHOT'
     }
 
     def "reports failure publishing when model validation fails"() {
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy
index 5b0c818..81c7b8f 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy
@@ -15,17 +15,25 @@
  */
 
 package org.gradle.api.publish.maven
+
+import org.gradle.api.credentials.Credentials
 import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
 import org.gradle.integtests.fixtures.publish.maven.AbstractMavenPublishIntegTest
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.MavenHttpModule
 import org.gradle.test.fixtures.server.http.MavenHttpRepository
 import org.junit.Rule
+import spock.lang.Issue
 import spock.lang.Unroll
 
+ at LeaksFileHandles
 class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
 
-    @Rule HttpServer server
+    @Rule
+    HttpServer server
+    @Rule
+    HttpServer redirectServer
 
     MavenHttpRepository mavenRemoteRepo
     MavenHttpModule module
@@ -37,6 +45,7 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
 
     def setup() {
         server.start()
+        redirectServer.start()
 
         mavenRemoteRepo = new MavenHttpRepository(server, repoPath, mavenRepo)
         group = "org.gradle"
@@ -45,39 +54,13 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
         module = mavenRemoteRepo.module(group, name, version)
 
         settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'maven-publish'
-            version = '$version'
-            group = '$group'
-
-            publishing {
-                repositories {
-                    maven {
-                        url "$mavenRemoteRepo.uri"
-                    }
-                }
-                publications {
-                    maven(MavenPublication) {
-                        from components.java
-                    }
-                }
-            }
-        """
+        buildFile << publicationBuild(version, group, mavenRemoteRepo.uri)
     }
 
     def "can publish to an unauthenticated http repo"() {
         given:
-        module.artifact.expectPut()
-        module.artifact.sha1.expectPut()
-        module.artifact.md5.expectPut()
-        module.rootMetaData.expectGetMissing()
-        module.rootMetaData.expectPut()
-        module.rootMetaData.sha1.expectPut()
-        module.rootMetaData.md5.expectPut()
-        module.pom.expectPut()
-        module.pom.sha1.expectPut()
-        module.pom.md5.expectPut()
+        expectModulePublish(module)
+
         when:
         succeeds 'publish'
 
@@ -152,6 +135,7 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
 
         server.authenticationScheme = authScheme
         module.artifact.expectPut(401, credentials)
+        module.pom.expectPut(401, credentials)
 
         when:
         fails 'publish'
@@ -159,7 +143,7 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
         then:
         failure.assertHasDescription('Execution failed for task \':publishMavenPublicationToMavenRepository\'.')
         failure.assertHasCause('Failed to publish publication \'maven\' to repository \'maven\'')
-        failure.assertHasCause("Error deploying artifact 'org.gradle:publish:jar': Error deploying artifact: Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
+        failure.assertHasCause("Failed to deploy artifacts: Could not transfer artifact org.gradle:publish:jar:2 from/to remote (http://localhost:${server.port}/repo): Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
         // Cause goes missing through the maven classes, but does end up logged to stderr
         failure.error.contains("Could not PUT '${module.artifact.uri}'. Received status code 401 from server: Unauthorized")
 
@@ -172,6 +156,7 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
         given:
         server.authenticationScheme = authScheme
         module.artifact.expectPut(401)
+        module.pom.expectPut(401)
 
         when:
         fails 'publish'
@@ -179,11 +164,116 @@ class MavenPublishHttpIntegTest extends AbstractMavenPublishIntegTest {
         then:
         failure.assertHasDescription('Execution failed for task \':publishMavenPublicationToMavenRepository\'.')
         failure.assertHasCause('Failed to publish publication \'maven\' to repository \'maven\'')
-        failure.assertHasCause("Error deploying artifact 'org.gradle:publish:jar': Error deploying artifact: Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
+        failure.assertHasCause("Failed to deploy artifacts: Could not transfer artifact org.gradle:publish:jar:2 from/to remote (http://localhost:${server.port}/repo): Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
         // Cause goes missing through the maven classes, but does end up logged to stderr
         failure.error.contains("Could not PUT '${module.artifact.uri}'. Received status code 401 from server: Unauthorized")
 
         where:
         authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
     }
+
+    @Issue("GRADLE-3312")
+    def "can publish to a http repo via redirects"() {
+        given:
+        buildFile.text = publicationBuild(version, group, new URI("${redirectServer.uri}/repo"))
+
+        redirectServer.expectGetRedirected(module.rootMetaData.path, "${server.uri}${module.rootMetaData.path}")
+        module.rootMetaData.expectGetMissing()
+
+        expectRedirectPublish(module, server.getUri(), redirectServer)
+
+        when:
+        succeeds 'publish'
+
+        then:
+        def localPom = file("build/publications/maven/pom-default.xml").assertIsFile()
+        def localArtifact = file("build/libs/publish-2.jar").assertIsFile()
+
+        module.pomFile.assertIsCopyOf(localPom)
+        module.pom.verifyChecksums()
+        module.artifactFile.assertIsCopyOf(localArtifact)
+        module.artifact.verifyChecksums()
+
+        module.rootMetaData.verifyChecksums()
+        module.rootMetaData.versions == ["2"]
+    }
+
+    @Issue("GRADLE-3312")
+    def "can publish to an authenticated http repo via redirects"() {
+        given:
+        buildFile.text = publicationBuild(version, group, new URI("${redirectServer.uri}/repo"))
+
+        def credentials = new DefaultPasswordCredentials('username', 'password')
+        buildFile << """
+            publishing.repositories.maven.credentials {
+                username '${credentials.username}'
+                password '${credentials.password}'
+            }
+        """
+
+        redirectServer.expectGetRedirected(module.rootMetaData.path, "${server.uri}${module.rootMetaData.path}")
+        module.rootMetaData.expectGetMissing(credentials)
+
+        expectRedirectPublish(module, server.getUri(), redirectServer, credentials)
+
+        when:
+        succeeds 'publish'
+
+        then:
+        def localPom = file("build/publications/maven/pom-default.xml").assertIsFile()
+        def localArtifact = file("build/libs/publish-2.jar").assertIsFile()
+
+        module.pomFile.assertIsCopyOf(localPom)
+        module.pom.verifyChecksums()
+        module.artifactFile.assertIsCopyOf(localArtifact)
+        module.artifact.verifyChecksums()
+
+        module.rootMetaData.verifyChecksums()
+        module.rootMetaData.versions == ["2"]
+    }
+
+    private String publicationBuild(String version, String group, URI uri) {
+        return """
+            apply plugin: 'java'
+            apply plugin: 'maven-publish'
+            version = '$version'
+            group = '$group'
+
+            publishing {
+                repositories {
+                    maven {
+                        url "$uri"
+                    }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+    }
+
+    private void expectModulePublish(MavenHttpModule module) {
+        module.artifact.expectPut()
+        module.artifact.sha1.expectPut()
+        module.artifact.md5.expectPut()
+        module.rootMetaData.expectGetMissing()
+        module.rootMetaData.expectPut()
+        module.rootMetaData.sha1.expectPut()
+        module.rootMetaData.md5.expectPut()
+        module.pom.expectPut()
+        module.pom.sha1.expectPut()
+        module.pom.md5.expectPut()
+    }
+
+    private void expectRedirectPublish(MavenHttpModule module, URI targetServerUri, HttpServer httpServer, Credentials credentials = null) {
+        String redirectUri = targetServerUri.toString()
+        [module.artifact, module.pom, module.rootMetaData].each { artifact ->
+            [artifact, artifact.sha1, artifact.md5].each { innerArtifact ->
+                httpServer.expectPutRedirected(innerArtifact.path, "${redirectUri}${innerArtifact.path}")
+                innerArtifact.expectPut(credentials)
+            }
+        }
+    }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpsIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpsIntegTest.groovy
index 21c1c85..280d2b8 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpsIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpsIntegTest.groovy
@@ -20,8 +20,10 @@ import org.gradle.test.fixtures.keystore.TestKeyStore
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.MavenHttpModule
 import org.gradle.test.fixtures.server.http.MavenHttpRepository
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
+ at LeaksFileHandles
 class MavenPublishHttpsIntegTest extends AbstractMavenPublishIntegTest {
     TestKeyStore keyStore
 
@@ -77,7 +79,7 @@ class MavenPublishHttpsIntegTest extends AbstractMavenPublishIntegTest {
 
         then:
         failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
-        failure.assertHasCause("Error deploying artifact 'org.gradle:publish:jar': Error deploying artifact: Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
+        failure.assertHasCause("Failed to deploy artifacts: Could not transfer artifact org.gradle:publish:jar:2 from/to remote (https://localhost:${server.sslPort}/repo): Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
         // TODO:DAZ Get this exception into the cause
         failure.error.contains("peer not authenticated")
     }
@@ -94,7 +96,7 @@ class MavenPublishHttpsIntegTest extends AbstractMavenPublishIntegTest {
 
         then:
         failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
-        failure.assertHasCause("Error deploying artifact 'org.gradle:publish:jar': Error deploying artifact: Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
+        failure.assertHasCause("Failed to deploy artifacts: Could not transfer artifact org.gradle:publish:jar:2 from/to remote (https://localhost:${server.sslPort}/repo): Could not write to resource 'org/gradle/publish/2/publish-2.jar'")
         // TODO:DAZ Get this exception into the cause
         failure.error.contains("peer not authenticated")
     }
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
index 95deb61..80748cd 100644
--- 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
@@ -235,6 +235,9 @@ uploadArchives {
         then:
         def module = mavenRepo.module('org.gradle', 'test', '1.0-SNAPSHOT')
         module.assertArtifactsPublished("maven-metadata.xml", "test-${module.publishArtifactVersion}.jar", "test-${module.publishArtifactVersion}.pom")
+
+        and:
+        module.parsedPom.version == '1.0-SNAPSHOT'
     }
 
     def "can publish multiple deployments with attached artifacts"() {
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishNonUniqueSnapshotVersionTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishNonUniqueSnapshotVersionTest.groovy
new file mode 100644
index 0000000..0bb7ea4
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishNonUniqueSnapshotVersionTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+class MavenPublishNonUniqueSnapshotVersionTest extends AbstractIntegrationSpec {
+    public void "can publish a non-unique snapshot version"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle.test'
+version = '4.2-SNAPSHOT'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("${mavenRepo.uri}"))
+            uniqueVersion = false
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def module = mavenRepo.module("org.gradle.test", "publishTest", "4.2-SNAPSHOT")
+        module.withNonUniqueSnapshots()
+
+        module.assertArtifactsPublished("publishTest-4.2-SNAPSHOT.pom", "publishTest-4.2-SNAPSHOT.jar", "maven-metadata.xml")
+        module.getParsedPom().version == '4.2-SNAPSHOT'
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/AbstractMavenPublishAction.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/AbstractMavenPublishAction.java
index f8a4ad9..5f8f89e 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/AbstractMavenPublishAction.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/AbstractMavenPublishAction.java
@@ -16,185 +16,125 @@
 
 package org.gradle.api.publication.maven.internal.action;
 
-import com.beust.jcommander.internal.Lists;
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.DefaultArtifact;
-import org.apache.maven.artifact.handler.DefaultArtifactHandler;
-import org.apache.maven.artifact.manager.WagonManager;
-import org.apache.maven.artifact.metadata.ArtifactMetadata;
-import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.apache.maven.artifact.repository.DefaultArtifactRepository;
-import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
-import org.apache.maven.artifact.versioning.VersionRange;
-import org.apache.maven.project.artifact.AttachedArtifact;
-import org.apache.maven.project.artifact.ProjectArtifactMetadata;
-import org.codehaus.classworlds.ClassWorld;
-import org.codehaus.classworlds.DuplicateRealmException;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.repository.internal.MavenRepositorySystemSession;
+import org.codehaus.plexus.DefaultPlexusContainer;
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.PlexusContainerException;
 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
-import org.codehaus.plexus.embed.Embedder;
 import org.gradle.api.GradleException;
 import org.gradle.internal.UncheckedException;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.RepositorySystem;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.artifact.ArtifactType;
+import org.sonatype.aether.impl.internal.SimpleLocalRepositoryManager;
+import org.sonatype.aether.util.DefaultRepositorySystemSession;
+import org.sonatype.aether.util.artifact.DefaultArtifact;
 
 import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 abstract class AbstractMavenPublishAction implements MavenPublishAction {
-    private static ClassLoader plexusClassLoader;
+    private final PlexusContainer container;
+    private final DefaultRepositorySystemSession session;
 
-    protected final WagonManager wagonManager;
-    private final File pomFile;
-    private final List<AdditionalArtifact> additionalArtifacts = Lists.newArrayList();
-    private File mainArtifact;
-
-    private File localMavenRepository;
-    private PlexusContainer container;
+    private final List<Artifact> attached = new ArrayList<Artifact>();
+    private final Artifact pomArtifact;
+    private Artifact mainArtifact;
 
     protected AbstractMavenPublishAction(File pomFile) {
-        this.pomFile = pomFile;
-        this.wagonManager = lookup(WagonManager.class);
-        wagonManager.setDownloadMonitor(new LoggingMavenTransferListener());
+        container = newPlexusContainer();
+        session = new MavenRepositorySystemSession();
+        session.setTransferListener(new LoggingMavenTransferListener());
+        session.getConfigProperties().put("maven.metadata.legacy", "true");
+
+        Model pom = parsePom(pomFile);
+        pomArtifact = new DefaultArtifact(pom.getGroupId(), pom.getArtifactId(), "pom", pom.getVersion()).setFile(pomFile);
+        mainArtifact = createTypedArtifact(pom.getPackaging(), null);
     }
 
     public void setLocalMavenRepositoryLocation(File localMavenRepository) {
-        this.localMavenRepository = localMavenRepository;
+        session.setLocalRepositoryManager(new SimpleLocalRepositoryManager(localMavenRepository));
     }
 
     public void setMainArtifact(File file) {
-        this.mainArtifact = file;
+        mainArtifact = mainArtifact.setFile(file);
     }
 
     @Override
     public void addAdditionalArtifact(File file, String type, String classifier) {
-        AdditionalArtifact artifact = new AdditionalArtifact();
-        artifact.setFile(file);
-        artifact.setType(type);
-        artifact.setClassifier(classifier);
-
-        additionalArtifacts.add(artifact);
+        attached.add(createTypedArtifact(type, classifier).setFile(file));
     }
 
     public void publish() {
-        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        List<Artifact> artifacts = new ArrayList<Artifact>();
+        if (mainArtifact.getFile() != null) {
+            artifacts.add(mainArtifact);
+        }
+        artifacts.add(pomArtifact);
+        artifacts.addAll(attached);
+
         try {
-            if (plexusClassLoader != null) {
-                Thread.currentThread().setContextClassLoader(plexusClassLoader);
-            }
-            doPublish();
-        } finally {
-            plexusClassLoader = Thread.currentThread().getContextClassLoader();
-            Thread.currentThread().setContextClassLoader(originalClassLoader);
+            publishArtifacts(artifacts, newRepositorySystem(), session);
+        } catch (RepositoryException e) {
+            throw new GradleException(e.getMessage(), e);
         }
     }
 
-    private void doPublish() {
-        ArtifactRepository localRepo = createLocalArtifactRepository();
-        ParsedMavenPom parsedMavenPom = new ParsedMavenPom(pomFile);
-
-        Artifact parentArtifact;
-        if (mainArtifact == null) {
-            Artifact pomArtifact = createPomArtifact(parsedMavenPom);
-            publishArtifact(pomArtifact, pomFile, localRepo);
-            parentArtifact = pomArtifact;
-        } else {
-            Artifact artifact = createMainArtifact(parsedMavenPom);
-            ArtifactMetadata metadata = new ProjectArtifactMetadata(artifact, pomFile);
-            artifact.addMetadata(metadata);
-            publishArtifact(artifact, mainArtifact, localRepo);
-            parentArtifact = artifact;
-        }
+    protected abstract void publishArtifacts(Collection<Artifact> artifact, RepositorySystem repositorySystem, RepositorySystemSession session) throws RepositoryException;
 
-        for (AdditionalArtifact attachedArtifact : additionalArtifacts) {
-            Artifact attach = createAttachedArtifact(parentArtifact, attachedArtifact.getType(), attachedArtifact.getClassifier());
-            publishArtifact(attach, attachedArtifact.getFile(), localRepo);
-        }
+    protected PlexusContainer getContainer() {
+        return container;
     }
 
-    protected abstract void publishArtifact(Artifact artifact, File artifactFile, ArtifactRepository localRepo);
-
-    private ArtifactRepository createLocalArtifactRepository() {
-        String localRepositoryLocation = localMavenRepository.toURI().toString();
-        return new DefaultArtifactRepository("local", localRepositoryLocation, new DefaultRepositoryLayout());
+    private PlexusContainer newPlexusContainer() {
+        try {
+            return new DefaultPlexusContainer();
+        } catch (PlexusContainerException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
     }
 
-    protected <T> T lookup(Class<T> type) {
-        String role = type.getName();
+    private RepositorySystem newRepositorySystem() {
         try {
-            @SuppressWarnings("unchecked")
-            T lookup1 = (T) getContainer().lookup(role);
-            return lookup1;
+            return container.lookup(RepositorySystem.class);
         } catch (ComponentLookupException e) {
-            throw new GradleException("Unable to find component: " + role, e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
-    protected synchronized PlexusContainer getContainer() {
-        if (container == null) {
+    private Model parsePom(File pomFile) {
+        FileReader reader = null;
+        try {
+            reader = new FileReader(pomFile);
+            return new MavenXpp3Reader().read(reader, false);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
             try {
-                ClassWorld classWorld = new ClassWorld();
-                classWorld.newRealm("plexus.core", getClass().getClassLoader());
-
-                Embedder embedder = new Embedder();
-                embedder.start(classWorld);
-
-                container = embedder.getContainer();
-            } catch (PlexusContainerException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            } catch (DuplicateRealmException e) {
+                reader.close();
+            } catch (IOException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
         }
-
-        return container;
-    }
-
-    private Artifact createMainArtifact(ParsedMavenPom pom) {
-        return new DefaultArtifact(pom.getGroup(), pom.getArtifactId(), VersionRange.createFromVersion(pom.getVersion()),
-                null, pom.getPackaging(), null, artifactHandler(pom.getPackaging()));
-    }
-
-    private Artifact createPomArtifact(ParsedMavenPom pom) {
-        return new DefaultArtifact(pom.getGroup(), pom.getArtifactId(), VersionRange.createFromVersion(pom.getVersion()),
-                null, "pom", null, artifactHandler("pom"));
-    }
-
-    private Artifact createAttachedArtifact(Artifact mainArtifact, String type, String classifier) {
-        return new AttachedArtifact(mainArtifact, type, classifier, artifactHandler(type));
     }
 
-    private DefaultArtifactHandler artifactHandler(String type) {
-        return new DefaultArtifactHandler(type);
-    }
-
-    private static class AdditionalArtifact {
-        File file;
-        String type;
-        String classifier;
-
-        public void setFile(File file) {
-            this.file = file;
-        }
-
-        public File getFile() {
-            return file;
-        }
-
-        public void setType(String type) {
-            this.type = type;
-        }
-
-        public String getType() {
-            return type;
-        }
-
-        public void setClassifier(String classifier) {
-            this.classifier = classifier;
-        }
-
-        public String getClassifier() {
-            return classifier;
+    private Artifact createTypedArtifact(String type, String classifier) {
+        String extension = type;
+        ArtifactType stereotype = session.getArtifactTypeRegistry().get(type);
+        if (stereotype != null) {
+            extension = stereotype.getExtension();
+            if (classifier == null) {
+                classifier = stereotype.getClassifier();
+            }
         }
+        return new DefaultArtifact(pomArtifact.getGroupId(), pomArtifact.getArtifactId(), classifier, extension, pomArtifact.getVersion());
     }
-
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/LoggingMavenTransferListener.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/LoggingMavenTransferListener.java
index 9179df5..5d75a16 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/LoggingMavenTransferListener.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/LoggingMavenTransferListener.java
@@ -16,50 +16,36 @@
 
 package org.gradle.api.publication.maven.internal.action;
 
-import org.apache.maven.wagon.events.TransferEvent;
-import org.apache.maven.wagon.events.TransferListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.sonatype.aether.transfer.AbstractTransferListener;
+import org.sonatype.aether.transfer.TransferEvent;
+import org.sonatype.aether.transfer.TransferEvent.RequestType;
 
-class LoggingMavenTransferListener implements TransferListener {
+class LoggingMavenTransferListener extends AbstractTransferListener {
     private static final Logger LOGGER = LoggerFactory.getLogger(LoggingMavenTransferListener.class);
     private static final int KILO = 1024;
 
-    protected void log(String message) {
-        LOGGER.info(message);
-    }
-
-    public void debug(String s) {
-        LOGGER.debug(s);
-    }
-
-    public void transferError(TransferEvent event) {
+    public void transferFailed(TransferEvent event) {
         LOGGER.error(event.getException().getMessage());
     }
 
     public void transferInitiated(TransferEvent event) {
-        String message = event.getRequestType() == TransferEvent.REQUEST_PUT ? "Uploading" : "Downloading";
-        String dest = event.getRequestType() == TransferEvent.REQUEST_PUT ? " to " : " from ";
-
-        LOGGER.info(message + ": " + event.getResource().getName() + dest + "repository "
-                + event.getWagon().getRepository().getId() + " at " + event.getWagon().getRepository().getUrl());
+        String message = event.getRequestType() == RequestType.PUT ? "Uploading: {} to repository {} at {}" : "Downloading: {} from repository {} at {}";
+        LOGGER.info(message, event.getResource().getResourceName(), "remote", event.getResource().getRepositoryUrl());
     }
 
     public void transferStarted(TransferEvent event) {
         long contentLength = event.getResource().getContentLength();
         if (contentLength > 0) {
-            LOGGER.info("Transferring " + ((contentLength + KILO / 2) / KILO) + "K from "
-                    + event.getWagon().getRepository().getId());
+            LOGGER.info("Transferring {}K from remote", (contentLength + KILO / 2) / KILO);
         }
     }
 
-    public void transferProgress(TransferEvent event, byte[] bytes, int i) {
-    }
-
-    public void transferCompleted(TransferEvent event) {
+    public void transferSucceeded(TransferEvent event) {
         long contentLength = event.getResource().getContentLength();
-        if ((contentLength > 0) && (event.getRequestType() == TransferEvent.REQUEST_PUT)) {
-            LOGGER.info("Uploaded " + ((contentLength + KILO / 2) / KILO) + "K");
+        if (contentLength > 0 && event.getRequestType() == RequestType.PUT) {
+            LOGGER.info("Uploaded {}K", (contentLength + KILO / 2) / KILO);
         }
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenDeployAction.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenDeployAction.java
index 450c2b4..221812b 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenDeployAction.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenDeployAction.java
@@ -15,29 +15,28 @@
  */
 package org.gradle.api.publication.maven.internal.action;
 
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.ant.Authentication;
-import org.apache.maven.artifact.ant.Proxy;
+import java.io.File;
+import java.util.Collection;
+
 import org.apache.maven.artifact.ant.RemoteRepository;
-import org.apache.maven.artifact.deployer.ArtifactDeployer;
-import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
-import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.apache.maven.artifact.repository.DefaultArtifactRepository;
-import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
+import org.sonatype.aether.RepositorySystem;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.deployment.DeployRequest;
+import org.sonatype.aether.deployment.DeploymentException;
+import org.sonatype.aether.repository.Authentication;
+import org.sonatype.aether.repository.Proxy;
+import org.sonatype.aether.util.repository.DefaultProxySelector;
 import org.gradle.api.GradleException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-
 public class MavenDeployAction extends AbstractMavenPublishAction {
     private static final Logger LOGGER = LoggerFactory.getLogger(MavenDeployAction.class);
 
     private RemoteRepository remoteRepository;
-
     private RemoteRepository remoteSnapshotRepository;
-
-    private boolean uniqueVersion = true;
+    private SnapshotVersionManager snapshotVersionManager = new SnapshotVersionManager();
 
     public MavenDeployAction(File pomFile) {
         super(pomFile);
@@ -49,49 +48,50 @@ public class MavenDeployAction extends AbstractMavenPublishAction {
     }
 
     public void setUniqueVersion(boolean uniqueVersion) {
-        this.uniqueVersion = uniqueVersion;
+        snapshotVersionManager.setUniqueVersion(uniqueVersion);
     }
 
-    protected void publishArtifact(Artifact artifact, File artifactFile, ArtifactRepository localRepo) {
-        ArtifactDeployer deployer = lookup(ArtifactDeployer.class);
-        ArtifactRepository deploymentRepository = getRemoteArtifactRepository(artifact);
-
-        LOGGER.info("Deploying to " + deploymentRepository.getUrl());
-
-        try {
-            deployer.deploy(artifactFile, artifact, deploymentRepository, localRepo);
-        } catch (ArtifactDeploymentException e) {
-            throw new GradleException("Error deploying artifact '" + artifact.getDependencyConflictId() + "': " + e.getMessage(), e);
+    @Override
+    protected void publishArtifacts(Collection<Artifact> artifacts, RepositorySystem repositorySystem, RepositorySystemSession session) throws DeploymentException {
+        RemoteRepository gradleRepo = remoteRepository;
+        if (artifacts.iterator().next().isSnapshot() && remoteSnapshotRepository != null) {
+            gradleRepo = remoteSnapshotRepository;
         }
-    }
-
-    private ArtifactRepository getRemoteArtifactRepository(Artifact artifact) {
-        RemoteRepository deploymentRepository = remoteRepository;
-        if (artifact.isSnapshot() && remoteSnapshotRepository != null) {
-            deploymentRepository = remoteSnapshotRepository;
+        if (gradleRepo == null) {
+            throw new GradleException("Must specify a repository for deployment");
         }
 
-        if (deploymentRepository == null) {
-            throw new GradleException("Must specify a repository for deployment");
+        org.sonatype.aether.repository.RemoteRepository aetherRepo = createRepository(gradleRepo);
+
+        DeployRequest request = new DeployRequest();
+        request.setRepository(aetherRepo);
+        for (Artifact artifact : artifacts) {
+            request.addArtifact(artifact);
         }
 
-        // The repository id is used for `maven-metadata-${repository.id}.xml`, and to match credentials to repository.
-        initWagonManagerWithRepositorySettings("remote", deploymentRepository);
-        return new DefaultArtifactRepository("remote", deploymentRepository.getUrl(), new DefaultRepositoryLayout(), uniqueVersion);
+        snapshotVersionManager.install(repositorySystem);
+
+        LOGGER.info("Deploying to " + gradleRepo.getUrl());
+        repositorySystem.deploy(session, request);
     }
 
-    private void initWagonManagerWithRepositorySettings(String repositoryId, RemoteRepository repository) {
-        Authentication authentication = repository.getAuthentication();
-        if (authentication != null) {
-            wagonManager.addAuthenticationInfo(repositoryId, authentication.getUserName(),
-                    authentication.getPassword(), authentication.getPrivateKey(),
-                    authentication.getPassphrase());
+    private org.sonatype.aether.repository.RemoteRepository createRepository(RemoteRepository gradleRepo) {
+        org.sonatype.aether.repository.RemoteRepository repo = new org.sonatype.aether.repository.RemoteRepository("remote",
+                        gradleRepo.getLayout(), gradleRepo.getUrl());
+
+        org.apache.maven.artifact.ant.Authentication auth = gradleRepo.getAuthentication();
+        if (auth != null) {
+            repo.setAuthentication(new Authentication(auth.getUserName(), auth.getPassword(), auth.getPrivateKey(), auth.getPassphrase()));
         }
 
-        Proxy proxy = repository.getProxy();
+        org.apache.maven.artifact.ant.Proxy proxy = gradleRepo.getProxy();
         if (proxy != null) {
-            wagonManager.addProxy(proxy.getType(), proxy.getHost(), proxy.getPort(), proxy.getUserName(),
-                    proxy.getPassword(), proxy.getNonProxyHosts());
+            DefaultProxySelector proxySelector = new DefaultProxySelector();
+            Authentication proxyAuth = new Authentication(proxy.getUserName(), proxy.getPassword());
+            proxySelector.add(new Proxy(proxy.getType(), proxy.getHost(), proxy.getPort(), proxyAuth), proxy.getNonProxyHosts());
+            repo.setProxy(proxySelector.getProxy(repo));
         }
+
+        return repo;
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenInstallAction.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenInstallAction.java
index 51a452a..f3c0283 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenInstallAction.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenInstallAction.java
@@ -15,26 +15,28 @@
  */
 package org.gradle.api.publication.maven.internal.action;
 
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.installer.ArtifactInstallationException;
-import org.apache.maven.artifact.installer.ArtifactInstaller;
-import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.gradle.api.GradleException;
-
 import java.io.File;
+import java.util.Collection;
+
+import org.sonatype.aether.RepositorySystem;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.installation.InstallRequest;
+import org.sonatype.aether.installation.InstallationException;
 
 public class MavenInstallAction extends AbstractMavenPublishAction {
+
     public MavenInstallAction(File pomFile) {
         super(pomFile);
     }
 
     @Override
-    protected void publishArtifact(Artifact artifact, File artifactFile, ArtifactRepository localRepo) {
-        ArtifactInstaller installer = lookup(ArtifactInstaller.class);
-        try {
-            installer.install(artifactFile, artifact, localRepo);
-        } catch (ArtifactInstallationException e) {
-            throw new GradleException("Error installing artifact '" + artifact.getDependencyConflictId() + "': " + e.getMessage(), e);
+    protected void publishArtifacts(Collection<Artifact> artifacts, RepositorySystem repositorySystem, RepositorySystemSession session) throws InstallationException {
+        InstallRequest request = new InstallRequest();
+        for (Artifact artifact : artifacts) {
+            request.addArtifact(artifact);
         }
+
+        repositorySystem.install(session, request);
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenWagonDeployAction.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenWagonDeployAction.java
index 316904c..9d6564f 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenWagonDeployAction.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/MavenWagonDeployAction.java
@@ -16,9 +16,8 @@
 
 package org.gradle.api.publication.maven.internal.action;
 
-import org.codehaus.plexus.PlexusContainerException;
-
 import java.io.File;
+import java.net.MalformedURLException;
 
 /**
  * A deploy action that uses the baked in Maven wagon implementations, or a custom user-provided wagon implemented.
@@ -30,8 +29,8 @@ public class MavenWagonDeployAction extends MavenDeployAction {
 
     public void addWagonJar(File jar) {
         try {
-            getContainer().addJarResource(jar);
-        } catch (PlexusContainerException e) {
+            getContainer().getContainerRealm().addURL(jar.toURI().toURL());
+        } catch (MalformedURLException e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/ParsedMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/ParsedMavenPom.java
deleted file mode 100644
index 225eac5..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/ParsedMavenPom.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.action;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
-import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
-import org.gradle.api.GradleException;
-
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-
-class ParsedMavenPom {
-    private final Model model;
-
-    public ParsedMavenPom(File pomFile) {
-        try {
-            model = parsePom(pomFile);
-        } catch (Exception e) {
-            throw new GradleException("Cannot read generated POM!", e);
-        }
-    }
-
-    private Model parsePom(File pomFile) throws IOException, XmlPullParserException {
-        FileReader reader = new FileReader(pomFile);
-        try {
-            return new MavenXpp3Reader().read(reader, false);
-        } finally {
-            IOUtils.closeQuietly(reader);
-        }
-    }
-
-    public String getGroup() {
-        return model.getGroupId();
-    }
-
-    public String getArtifactId() {
-        return model.getArtifactId();
-    }
-
-    public String getVersion() {
-        return model.getVersion();
-    }
-
-    public String getPackaging() {
-        return model.getPackaging();
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/SnapshotVersionManager.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/SnapshotVersionManager.java
new file mode 100644
index 0000000..7ca2c92
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/action/SnapshotVersionManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.action;
+
+import org.gradle.internal.UncheckedException;
+import org.sonatype.aether.RepositorySystem;
+import org.sonatype.aether.RepositorySystemSession;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.deployment.DeployRequest;
+import org.sonatype.aether.impl.MetadataGenerator;
+import org.sonatype.aether.impl.MetadataGeneratorFactory;
+import org.sonatype.aether.impl.internal.DefaultDeployer;
+import org.sonatype.aether.impl.internal.DefaultRepositorySystem;
+import org.sonatype.aether.installation.InstallRequest;
+import org.sonatype.aether.metadata.Metadata;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+
+class SnapshotVersionManager implements MetadataGeneratorFactory, MetadataGenerator {
+    private boolean uniqueVersion = true;
+
+    public void setUniqueVersion(boolean uniqueVersion) {
+        this.uniqueVersion = uniqueVersion;
+    }
+
+    public void install(RepositorySystem repositorySystem) {
+        try {
+            Field field = DefaultRepositorySystem.class.getDeclaredField("deployer");
+            field.setAccessible(true);
+            DefaultDeployer deployer = (DefaultDeployer) field.get(repositorySystem);
+            deployer.addMetadataGeneratorFactory(this);
+        } catch (NoSuchFieldException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } catch (IllegalAccessException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    @Override
+    public int getPriority() {
+        return -100;
+    }
+
+    @Override
+    public MetadataGenerator newInstance(RepositorySystemSession session, InstallRequest request) {
+        return null;
+    }
+
+    @Override
+    public MetadataGenerator newInstance(RepositorySystemSession session, DeployRequest request) {
+        return uniqueVersion ? null : this;
+    }
+
+    @Override
+    public Collection<? extends Metadata> prepare(Collection<? extends Artifact> artifacts) {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Artifact transformArtifact(Artifact artifact) {
+        if (artifact.isSnapshot()) {
+            artifact = artifact.setVersion(artifact.getBaseVersion());
+        }
+        return artifact;
+    }
+
+    @Override
+    public Collection<? extends Metadata> finish(Collection<? extends Artifact> artifacts) {
+        return Collections.emptySet();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/deployer/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/deployer/AbstractMavenResolver.java
index d9041ca..fe8b7f5 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/deployer/AbstractMavenResolver.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/deployer/AbstractMavenResolver.java
@@ -16,6 +16,7 @@
 package org.gradle.api.publication.maven.internal.deployer;
 
 import groovy.lang.Closure;
+
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
@@ -37,7 +38,7 @@ import org.gradle.internal.component.external.model.IvyModuleArtifactPublishMeta
 import org.gradle.internal.component.external.model.IvyModulePublishMetaData;
 import org.gradle.listener.ActionBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.mvn3.org.apache.maven.settings.building.SettingsBuildingException;
+import org.apache.maven.settings.building.SettingsBuildingException;
 
 import java.io.File;
 import java.util.Set;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultMavenPom.java
index 2c6a4d6..1b3ce31 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultMavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultMavenPom.java
@@ -16,8 +16,10 @@
 package org.gradle.api.publication.maven.internal.pom;
 
 import groovy.lang.Closure;
+
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
 import org.apache.maven.project.MavenProject;
 import org.codehaus.groovy.runtime.InvokerHelper;
 import org.gradle.api.Action;
@@ -43,7 +45,7 @@ public class DefaultMavenPom implements MavenPom {
 
     private PomDependenciesConverter pomDependenciesConverter;
     private FileResolver fileResolver;
-    private MavenProject mavenProject = new MavenProject();
+    private Model model = new MavenProject().getModel();
     private Conf2ScopeMappingContainer scopeMappings;
     private ActionBroadcast<MavenPom> whenConfiguredActions = new ActionBroadcast<MavenPom>();
     private XmlTransformer withXmlActions = new XmlTransformer();
@@ -55,7 +57,7 @@ public class DefaultMavenPom implements MavenPom {
         this.scopeMappings = scopeMappings;
         this.pomDependenciesConverter = pomDependenciesConverter;
         this.fileResolver = fileResolver;
-        mavenProject.setModelVersion("4.0.0");
+        model.setModelVersion("4.0.0");
     }
 
     public Conf2ScopeMappingContainer getScopeMappings() {
@@ -133,20 +135,20 @@ public class DefaultMavenPom implements MavenPom {
     }
 
     public Model getModel() {
-        return mavenProject.getModel();
+        return model;
     }
 
     public DefaultMavenPom setModel(Object model) {
-        this.mavenProject = new MavenProject((Model) model);
+        this.model = (Model) model;
         return this;
     }
 
     public MavenProject getMavenProject() {
-        return mavenProject;
+        return new MavenProject(model);
     }
 
     public DefaultMavenPom setMavenProject(MavenProject mavenProject) {
-        this.mavenProject = mavenProject;
+        this.model = mavenProject.getModel();
         return this;
     }
 
@@ -160,11 +162,7 @@ public class DefaultMavenPom implements MavenPom {
 
     public DefaultMavenPom getEffectivePom() {
         DefaultMavenPom effectivePom = new DefaultMavenPom(null, this.scopeMappings, pomDependenciesConverter, fileResolver);
-        try {
-            effectivePom.setMavenProject((MavenProject) mavenProject.clone());
-        } catch (CloneNotSupportedException e) {
-            throw new RuntimeException(e);
-        }
+        effectivePom.setModel(model.clone());
         effectivePom.getDependencies().addAll(getGeneratedDependencies());
         effectivePom.withXmlActions = withXmlActions;
         whenConfiguredActions.execute(effectivePom);
@@ -206,7 +204,7 @@ public class DefaultMavenPom implements MavenPom {
         try {
             withXmlActions.transform(pomWriter, POM_FILE_ENCODING, new ErroringAction<Writer>() {
                 protected void doExecute(Writer writer) throws IOException {
-                    mavenProject.writeModel(writer);
+                    new MavenXpp3Writer().write(writer, getModel());
                 }
             });
         } finally {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultPomDependenciesConverter.java
index fde1945..c560eac 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultPomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/DefaultPomDependenciesConverter.java
@@ -34,10 +34,10 @@ class DefaultPomDependenciesConverter implements PomDependenciesConverter {
         this.versionRangeMapper = versionRangeMapper;
     }
 
-    public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
+    public List<Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
         Map<ModuleDependency, Set<Configuration>> dependencyToConfigurations = createDependencyToConfigurationsMap(configurations);
         Map<ModuleDependency, String> dependenciesMap = createDependencyToScopeMap(conf2ScopeMappingContainer, dependencyToConfigurations);
-        List<org.apache.maven.model.Dependency> mavenDependencies = new ArrayList<org.apache.maven.model.Dependency>();
+        List<Dependency> mavenDependencies = new ArrayList<Dependency>();
         for (ModuleDependency dependency : dependenciesMap.keySet()) {
             String scope = dependenciesMap.get(dependency);
             Set<Configuration> dependencyConfigurations = dependencyToConfigurations.get(dependency);
@@ -123,7 +123,6 @@ class DefaultPomDependenciesConverter implements PomDependenciesConverter {
         mavenDependency.setVersion(mapToMavenSyntax(dependency.getVersion()));
         mavenDependency.setType(type);
         mavenDependency.setScope(scope);
-        mavenDependency.setOptional(false);
         mavenDependency.setClassifier(classifier);
         mavenDependency.setExclusions(getExclusions(dependency, configurations));
         return mavenDependency;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/PlexusLoggerAdapter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/PlexusLoggerAdapter.java
index ff4e09a..8156299 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/PlexusLoggerAdapter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pom/PlexusLoggerAdapter.java
@@ -92,6 +92,10 @@ public class PlexusLoggerAdapter implements Logger {
         throw new UnsupportedOperationException();
     }
 
+    public void setThreshold(int arg0) {
+        throw new UnsupportedOperationException();
+    }
+
     public String getName() {
         return logger.getName();
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagon.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagon.java
index 4ce29f0..1337a32 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagon.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagon.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.publication.maven.internal.wagon;
 
-
 import org.apache.maven.wagon.ConnectionException;
 import org.apache.maven.wagon.ResourceDoesNotExistException;
 import org.apache.maven.wagon.TransferFailedException;
@@ -230,6 +229,16 @@ public class RepositoryTransportDeployWagon implements Wagon {
         return 0;
     }
 
+    @Override
+    public final void setReadTimeout(int i) {
+
+    }
+
+    @Override
+    public final int getReadTimeout() {
+        return 0;
+    }
+
     private SessionEvent sessionEvent(int e) {
         return new SessionEvent(this, e);
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/WagonRegistry.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/WagonRegistry.java
deleted file mode 100644
index c32c4da..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/wagon/WagonRegistry.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.publication.maven.internal.wagon;
-
-import org.apache.maven.wagon.Wagon;
-import org.codehaus.plexus.PlexusContainer;
-import org.codehaus.plexus.component.repository.ComponentDescriptor;
-import org.codehaus.plexus.component.repository.exception.ComponentRepositoryException;
-import org.gradle.api.GradleException;
-
-public class WagonRegistry {
-    private static final String FAILED_TO_REGISTER_WAGON = "Failed to register wagon";
-    private PlexusContainer plexusContainer;
-
-    public WagonRegistry(PlexusContainer plexusContainer) {
-        this.plexusContainer = plexusContainer;
-    }
-
-    public void registerProtocol(String protocol) {
-        try {
-            ComponentDescriptor componentDescriptor = new ComponentDescriptor();
-            componentDescriptor.setRole(Wagon.ROLE);
-            componentDescriptor.setRoleHint(protocol);
-            componentDescriptor.setImplementation(RepositoryTransportDeployWagon.class.getCanonicalName());
-
-            plexusContainer.addComponentDescriptor(componentDescriptor);
-        } catch (ComponentRepositoryException e) {
-            throw new GradleException(FAILED_TO_REGISTER_WAGON, e);
-        }
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemotePublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemotePublisher.java
index 884862f..a2bc5e2 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemotePublisher.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenRemotePublisher.java
@@ -17,6 +17,7 @@
 package org.gradle.api.publish.maven.internal.publisher;
 
 import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.wagon.Wagon;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.credentials.Credentials;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
@@ -26,7 +27,6 @@ import org.gradle.api.publication.maven.internal.action.MavenDeployAction;
 import org.gradle.api.publication.maven.internal.action.MavenPublishAction;
 import org.gradle.api.publication.maven.internal.wagon.RepositoryTransportDeployWagon;
 import org.gradle.api.publication.maven.internal.wagon.RepositoryTransportWagonAdapter;
-import org.gradle.api.publication.maven.internal.wagon.WagonRegistry;
 import org.gradle.internal.Factory;
 import org.gradle.internal.artifacts.repositories.AuthenticationSupportedInternal;
 import org.gradle.logging.LoggingManagerInternal;
@@ -48,7 +48,6 @@ public class MavenRemotePublisher extends AbstractMavenPublisher {
         GradleWagonMavenDeployAction deployTask = new GradleWagonMavenDeployAction(pomFile, artifactRepository, repositoryTransportFactory);
         deployTask.setLocalMavenRepositoryLocation(temporaryDirFactory.create());
         deployTask.setRepositories(createMavenRemoteRepository(artifactRepository), null);
-        deployTask.setUniqueVersion(true);
         return deployTask;
     }
 
@@ -64,20 +63,19 @@ public class MavenRemotePublisher extends AbstractMavenPublisher {
     private static class GradleWagonMavenDeployAction extends MavenDeployAction {
         private final MavenArtifactRepository artifactRepository;
         private final RepositoryTransportFactory repositoryTransportFactory;
-        private final WagonRegistry wagonRegistry;
 
         public GradleWagonMavenDeployAction(File pomFile, MavenArtifactRepository artifactRepository, RepositoryTransportFactory repositoryTransportFactory) {
             super(pomFile);
             this.artifactRepository = artifactRepository;
             this.repositoryTransportFactory = repositoryTransportFactory;
-            this.wagonRegistry = new WagonRegistry(getContainer());
 
             registerWagonProtocols();
         }
 
         private void registerWagonProtocols() {
+            Wagon wagon = new RepositoryTransportDeployWagon();
             for (String protocol : repositoryTransportFactory.getRegisteredProtocols()) {
-                wagonRegistry.registerProtocol(protocol);
+                getContainer().addComponent(wagon, Wagon.class, protocol);
             }
         }
 
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 36a60c5..636b6aa 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
@@ -22,9 +22,9 @@ 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.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 org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileReader;
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 07ea134..c43beab 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
@@ -19,7 +19,7 @@ package org.gradle.api.publish.maven.internal.tasks;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Exclusion;
 import org.apache.maven.model.Model;
-import org.apache.maven.project.MavenProject;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
 import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.XmlProvider;
@@ -40,13 +40,13 @@ public class MavenPomFileGenerator {
     private static final String POM_FILE_ENCODING = "UTF-8";
     private static final String POM_VERSION = "4.0.0";
 
-    private MavenProject mavenProject = new MavenProject();
+    private Model model = new Model();
     private XmlTransformer xmlTransformer = new XmlTransformer();
     private final VersionRangeMapper versionRangeMapper;
 
     public MavenPomFileGenerator(MavenProjectIdentity identity, VersionRangeMapper versionRangeMapper) {
         this.versionRangeMapper = versionRangeMapper;
-        mavenProject.setModelVersion(POM_VERSION);
+        model.setModelVersion(POM_VERSION);
         Model model = getModel();
         model.setGroupId(identity.getGroupId());
         model.setArtifactId(identity.getArtifactId());
@@ -59,7 +59,7 @@ public class MavenPomFileGenerator {
     }
 
     private Model getModel() {
-        return mavenProject.getModel();
+        return model;
     }
 
     public void addRuntimeDependency(MavenDependencyInternal dependency) {
@@ -108,7 +108,7 @@ public class MavenPomFileGenerator {
         xmlTransformer.transform(file, POM_FILE_ENCODING, new Action<Writer>() {
             public void execute(Writer writer) {
                 try {
-                    mavenProject.writeModel(writer);
+                    new MavenXpp3Writer().write(writer, model);
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);
                 }
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 ab2e030..05afd20 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
@@ -38,10 +38,10 @@ import org.gradle.api.publish.plugins.PublishingPlugin;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.typeconversion.NotationParser;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.Path;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 
 import javax.inject.Inject;
 import java.io.File;
@@ -92,7 +92,7 @@ public class MavenPublishPlugin implements Plugin<Project> {
     static class Rules extends RuleSource {
         @Mutate
         @SuppressWarnings("UnusedDeclaration")
-        public void realizePublishingTasks(CollectionBuilder<Task> tasks, PublishingExtension extension, @Path("buildDir") File buildDir) {
+        public void realizePublishingTasks(ModelMap<Task> tasks, PublishingExtension extension, @Path("buildDir") File buildDir) {
             // Create generatePom tasks for any Maven publication
             PublicationContainer publications = extension.getPublications();
             Task publishLifecycleTask = tasks.get(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
@@ -107,7 +107,7 @@ public class MavenPublishPlugin implements Plugin<Project> {
             }
         }
 
-        private void createPublishTasksForEachMavenRepo(CollectionBuilder<Task> tasks, PublishingExtension extension, final Task publishLifecycleTask, final MavenPublicationInternal publication,
+        private void createPublishTasksForEachMavenRepo(ModelMap<Task> tasks, PublishingExtension extension, final Task publishLifecycleTask, final MavenPublicationInternal publication,
                                                         final String publicationName) {
             for (final MavenArtifactRepository repository : extension.getRepositories().withType(MavenArtifactRepository.class)) {
                 final String repositoryName = repository.getName();
@@ -127,7 +127,7 @@ public class MavenPublishPlugin implements Plugin<Project> {
             }
         }
 
-        private void createLocalInstallTask(CollectionBuilder<Task> tasks, final Task publishLocalLifecycleTask, final MavenPublicationInternal publication, final String publicationName) {
+        private void createLocalInstallTask(ModelMap<Task> tasks, final Task publishLocalLifecycleTask, final MavenPublicationInternal publication, final String publicationName) {
             final String installTaskName = String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
 
             tasks.create(installTaskName, PublishToMavenLocal.class, new Action<PublishToMavenLocal>() {
@@ -140,7 +140,7 @@ public class MavenPublishPlugin implements Plugin<Project> {
             publishLocalLifecycleTask.dependsOn(installTaskName);
         }
 
-        private void createGeneratePomTask(CollectionBuilder<Task> tasks, final MavenPublicationInternal publication, String publicationName, final File buildDir) {
+        private void createGeneratePomTask(ModelMap<Task> tasks, final MavenPublicationInternal publication, String publicationName, final File buildDir) {
             String descriptorTaskName = String.format("generatePomFileFor%sPublication", capitalize(publicationName));
             tasks.create(descriptorTaskName, GenerateMavenPom.class, new Action<GenerateMavenPom>() {
                 public void execute(final GenerateMavenPom generatePomTask) {
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagonTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagonTest.groovy
index 3226623..ad46374 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagonTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/wagon/RepositoryTransportDeployWagonTest.groovy
@@ -146,7 +146,7 @@ class RepositoryTransportDeployWagonTest extends Specification {
         def failure = new IOException("failed")
         RepositoryTransportWagonAdapter delegate = Mock()
         delegate.putRemoteFile(*_) >> { LocalResource resource, String resourceName ->
-            resource.open()
+            resource.open().close()
             throw failure
         }
 
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 419dc0d..02ebcac 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
@@ -22,7 +22,7 @@ import org.gradle.api.publication.maven.internal.VersionRangeMapper
 import org.gradle.api.publish.maven.InvalidMavenPublicationException
 import org.gradle.api.publish.maven.MavenArtifact
 import org.gradle.api.publish.maven.internal.tasks.MavenPomFileGenerator
-import org.gradle.mvn3.org.codehaus.plexus.util.xml.pull.XmlPullParserException
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import spock.lang.Shared
 import spock.lang.Specification
diff --git a/subprojects/model-core/model-core.gradle b/subprojects/model-core/model-core.gradle
index 42ab655..d9dad15 100644
--- a/subprojects/model-core/model-core.gradle
+++ b/subprojects/model-core/model-core.gradle
@@ -39,5 +39,6 @@ dependencies {
 }
 
 useTestFixtures()
-useClassycle()
-strictCompile()
\ No newline at end of file
+//Re-enable classycle after removal of CollectionBuilder
+//useClassycle()
+strictCompileIgnoreDeprecations()
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ConfigurationCycleIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ConfigurationCycleIntegrationTest.groovy
index b191555..f87e8f7 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ConfigurationCycleIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ConfigurationCycleIntegrationTest.groovy
@@ -27,9 +27,6 @@ class ConfigurationCycleIntegrationTest extends AbstractIntegrationSpec {
 
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class Rules extends RuleSource {
                 @Model
                 String first(@Path("second") String second) {
@@ -47,7 +44,7 @@ class ConfigurationCycleIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void connectTasksToFirst(CollectionBuilder<Task> tasks, @Path("first") String first) {
+                void connectTasksToFirst(ModelMap<Task> tasks, @Path("first") String first) {
                 }
             }
 
@@ -65,9 +62,101 @@ class ConfigurationCycleIntegrationTest extends AbstractIntegrationSpec {
 
         and:
         failure.assertHasCause("""A cycle has been detected in model rule dependencies. References forming the cycle:
-Rules#first(java.lang.String) parameter 1 (path: second)
-  \\--- model.second @ build file '${buildFile}' line 29, column 17 @ line 30 (path: third)
-    \\--- Rules#third(java.lang.String) parameter 1 (path: first)
-      \\--- Rules#first(java.lang.String)""")
+first
+\\- Rules#first(java.lang.String)
+   \\- second
+      \\- model.second @ build file '${buildFile}' line 26, column 17
+         \\- third
+            \\- Rules#third(java.lang.String)
+               \\- first""")
+    }
+
+    def "cycles involving multiple rules of same phase are detected"() {
+        given:
+        EnableModelDsl.enable(executer)
+
+        when:
+        buildScript '''
+            class Rules extends RuleSource {
+                @Model List<String> m1() { [] }
+                @Model List<String> m2() { [] }
+                @Model List<String> m3() { [] }
+
+                @Mutate void m2ToM1(@Path("m1") m1, @Path("m2") m2) {
+                    if (!m1.empty) {
+                        throw new IllegalStateException("m2ToM1 has executed twice")
+                    }
+                    m1 << "executed"
+                }
+
+                // in cycle…
+                @Mutate void m3ToM1(@Path("m1") m1, @Path("m3") m3) {}
+                @Mutate void m1ToM3(@Path("m3") m3, @Path("m1") m1) {}
+
+                @Mutate void addTask(ModelMap<Task> tasks, @Path("m1") m1) {}
+            }
+
+            apply type: Rules
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("""A cycle has been detected in model rule dependencies. References forming the cycle:
+m1
+\\- Rules#m3ToM1(java.lang.Object, java.lang.Object)
+   \\- m3
+      \\- Rules#m1ToM3(java.lang.Object, java.lang.Object)
+         \\- m1""")
+    }
+
+    def "cycles involving multiple rules of different phase are detected"() {
+        given:
+        EnableModelDsl.enable(executer)
+
+        when:
+        buildScript '''
+            class Rules extends RuleSource {
+                @Model List<String> m1() { [] }
+                @Model List<String> m2() { [] }
+                @Model List<String> m3() { [] }
+
+                @Defaults void addM1Defaults(@Path("m1") m1) {
+                    if (!m1.empty) {
+                        throw new IllegalStateException("addM1Defaults has executed twice")
+                    }
+                    m1 << "addM3Defaults executed"
+                }
+
+                @Mutate void m2ToM1(@Path("m1") m1, @Path("m2") m2) {
+                    if (m1.size() > 1) {
+                        throw new IllegalStateException("m2ToM1 has executed twice")
+                    }
+                    m1 << "m2ToM1 executed"
+                }
+
+
+                // in cycle…
+                @Mutate void m3ToM1(@Path("m1") m1, @Path("m3") m3) {}
+                @Mutate void m1ToM3(@Path("m3") m3, @Path("m1") m1) {}
+
+                @Mutate void addTask(ModelMap<Task> tasks, @Path("m1") m1) {}
+            }
+
+            apply type: Rules
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("""A cycle has been detected in model rule dependencies. References forming the cycle:
+m1
+\\- Rules#m3ToM1(java.lang.Object, java.lang.Object)
+   \\- m3
+      \\- Rules#m1ToM3(java.lang.Object, java.lang.Object)
+         \\- m1""")
+
     }
 }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelReuseIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelReuseIntegrationTest.groovy
index c43881e..8aadcc2 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelReuseIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelReuseIntegrationTest.groovy
@@ -15,29 +15,19 @@
  */
 
 package org.gradle.model
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.EnableModelDsl
-import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
 import org.gradle.model.internal.persist.ReusingModelRegistryStore
-import spock.lang.Ignore
 
-//@IgnoreIf({ GradleContextualExecuter.isDaemon() })
- at Ignore("failing builds randomly on windows: http://builds.gradle.org/viewLog.html?buildId=276330&buildTypeId=Gradle_Master_Coverage_WindowsJava18_2")
-class ModelReuseIntegrationTest extends AbstractIntegrationSpec {
+// Requires daemon because reuse right now doesn't handle the build actually changing
+class ModelReuseIntegrationTest extends DaemonIntegrationSpec {
 
     def setup() {
-        executer = new DaemonGradleExecuter(distribution, testDirectoryProvider)
+        EnableModelDsl.enable(executer)
+
         executer.beforeExecute {
-            requireIsolatedDaemons()
             withArgument("-D$ReusingModelRegistryStore.TOGGLE=true")
-            withDaemonIdleTimeoutSecs(5)
         }
-        EnableModelDsl.enable(executer)
-    }
-
-    def cleanup() {
-        executer.withArgument("--stop").run()
     }
 
     String hashFor(String prefix) {
@@ -86,32 +76,9 @@ class ModelReuseIntegrationTest extends AbstractIntegrationSpec {
         taskHash != hashFor("task")
     }
 
-    def "can enable reuse with the component model"() {
-        when:
-        buildScript """
-            plugins {
-              id "org.gradle.jvm-component"
-              id "org.gradle.java-lang"
-            }
-
-            model {
-                components {
-                    create("main", JvmLibrarySpec)
-                }
-            }
-        """
-
-        then:
-        succeeds "build"
-        succeeds "build"
-    }
-
     def "can enable reuse with the variants benchmark"() {
         when:
         buildScript """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Flavour {
                 String getName()
@@ -139,15 +106,15 @@ class ModelReuseIntegrationTest extends AbstractIntegrationSpec {
 
             class VariantsRuleSource extends RuleSource {
                 @Model
-                void flavours(ManagedSet<Flavour> flavours) {
+                void flavours(ModelSet<Flavour> flavours) {
                 }
 
                 @Model
-                void types(ManagedSet<Type> types) {
+                void types(ModelSet<Type> types) {
                 }
 
                 @Model
-                void variants(ManagedSet<Variant> variants, ManagedSet<Flavour> flavours, ManagedSet<Type> types) {
+                void variants(ModelSet<Variant> variants, ModelSet<Flavour> flavours, ModelSet<Type> types) {
                     flavours.each { flavour ->
                         types.each { type ->
                             variants.create {
@@ -159,14 +126,14 @@ class ModelReuseIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addVariantTasks(CollectionBuilder<Task> tasks, ManagedSet<Variant> variants) {
+                void addVariantTasks(ModelMap<Task> tasks, ModelSet<Variant> variants) {
                     variants.each {
                         tasks.create(it.name)
                     }
                 }
 
                 @Mutate
-                void addAllVariantsTasks(CollectionBuilder<Task> tasks, ManagedSet<Variant> variants) {
+                void addAllVariantsTasks(ModelMap<Task> tasks, ModelSet<Variant> variants) {
                     tasks.create("allVariants") { allVariants ->
                         variants.each {
                             allVariants.dependsOn it.name
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingFailureIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingFailureIntegrationTest.groovy
index b307997..193dbd6 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingFailureIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingFailureIntegrationTest.groovy
@@ -33,8 +33,6 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
     def "unbound rules are reported"() {
         given:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class MyThing1 {}
                 static class MyThing2 {}
@@ -46,10 +44,8 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
                         new MyThing1()
                     }
 
-
                     @Mutate
                     void mutateThing2(MyThing2 thing2, MyThing3 thing3) {
-
                     }
                 }
             }
@@ -96,13 +92,10 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
     def "suggestions are provided for unbound rules"() {
         given:
         buildScript """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks) {
+                    void addTasks(ModelMap<Task> tasks) {
                         tasks.create("foobar")
                         tasks.create("raboof")
                     }
@@ -122,7 +115,7 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         failure.assertHasCause("""The following model rules are unbound:
-  model.tasks.foonar @ build file '${buildFile}' line 18, column 17
+  model.tasks.foonar @ build file '${buildFile}' line 15, column 17
     Mutable:
       - tasks.foonar (java.lang.Object) - suggestions: tasks.foobar""")
     }
@@ -130,8 +123,6 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
     def "ambiguous binding integration test"() {
         given:
         buildScript """
-            import org.gradle.model.*
-
             class Plugin1 {
                 static class Rules extends RuleSource {
                     @Model
@@ -168,7 +159,7 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
         fails "tasks"
 
         then:
-        failure.assertHasDescription("A problem occurred configuring root project")
+        failure.assertHasDescription("A problem occurred evaluating root project")
         failure.assertHasCause("There is a problem with model rule Plugin3\$Rules#m(java.lang.String).")
         failure.assertHasCause("""Type-only model reference of type java.lang.String (parameter 1) is ambiguous as multiple model elements are available for this type:
   - s1 (created by: Plugin1\$Rules#s1())
@@ -178,8 +169,6 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
     def "incompatible type binding"() {
         given:
         buildScript """
-            import org.gradle.model.*
-
             class Plugin1 {
                 static class Rules extends RuleSource {
                     @Mutate
@@ -199,6 +188,7 @@ class ModelRuleBindingFailureIntegrationTest extends AbstractIntegrationSpec {
         failure.assertHasCause("There is a problem with model rule Plugin1\$Rules#addTasks(java.lang.Integer).")
         failure.assertHasCause("""Model reference to element 'tasks' with type java.lang.Integer (parameter 1) is invalid due to incompatible types.
 This element was created by Project.<init>.tasks() and can be mutated as the following types:
+  - org.gradle.model.ModelMap<org.gradle.api.Task>
   - org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>
   - org.gradle.api.tasks.TaskContainer (or assignment compatible type thereof)""")
     }
@@ -206,8 +196,6 @@ This element was created by Project.<init>.tasks() and can be mutated as the fol
     def "unbound inputs for creator are reported"() {
         given:
         buildScript """
-            import org.gradle.model.*
-
             class Rules extends RuleSource {
                 @Model
                 Integer foo(@Path("bar") Integer bar) {
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingValidationIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingValidationIntegrationTest.groovy
index 3112c5c..629ed0b 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingValidationIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleBindingValidationIntegrationTest.groovy
@@ -32,12 +32,9 @@ class ModelRuleBindingValidationIntegrationTest extends AbstractIntegrationSpec
         """
 
         file("unused/build.gradle") << """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class Rules extends RuleSource {
                 @Mutate
-                void unbound(CollectionBuilder<Task> tasks, String unbound) {
+                void unbound(ModelMap<Task> tasks, String unbound) {
                 }
             }
 
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleCachingIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleCachingIntegrationTest.groovy
index 3712be0..53e6c2d 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleCachingIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleCachingIntegrationTest.groovy
@@ -16,16 +16,12 @@
 
 package org.gradle.model
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.integtests.fixtures.PersistentBuildProcessIntegrationTest
 import org.gradle.model.internal.inspect.ModelRuleExtractor
-import spock.lang.IgnoreIf
 
- at IgnoreIf({ !GradleContextualExecuter.longLivingProcess })
-class ModelRuleCachingIntegrationTest extends AbstractIntegrationSpec {
+class ModelRuleCachingIntegrationTest extends PersistentBuildProcessIntegrationTest {
 
     def setup() {
-        executer.requireIsolatedDaemons()
         buildFile << """
             def ruleCache = project.services.get($ModelRuleExtractor.name).cache
             def initialSize = ruleCache.size()
@@ -38,7 +34,7 @@ class ModelRuleCachingIntegrationTest extends AbstractIntegrationSpec {
         match[0][1] == "true"
     }
 
-    def "rules extracted from core plugins are reused across builds when using the daemon"() {
+    def "rules extracted from core plugins are reused across builds"() {
         given:
         buildFile << '''
             apply plugin: 'java-lang'
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleValidationIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleValidationIntegrationTest.groovy
index 888938d..6b82226 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleValidationIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ModelRuleValidationIntegrationTest.groovy
@@ -23,8 +23,6 @@ class ModelRuleValidationIntegrationTest extends AbstractIntegrationSpec {
     def "invalid model name produces error message"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model(" ")
@@ -49,8 +47,6 @@ class ModelRuleValidationIntegrationTest extends AbstractIntegrationSpec {
     def "model name can be at nested path"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model("foo. bar")
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/MutationRuleApplicationOrderIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/MutationRuleApplicationOrderIntegrationTest.groovy
index eaa6b14..7e88ddd 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/MutationRuleApplicationOrderIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/MutationRuleApplicationOrderIntegrationTest.groovy
@@ -41,9 +41,6 @@ class MutationRuleApplicationOrderIntegrationTest extends AbstractIntegrationSpe
     def "mutation rules from inner source classes applied via their common parent are executed in the order specified by class names of these rule sources"() {
         when:
         buildFile << '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MultipleRuleSources implements Plugin<Project> {
                 static class B extends RuleSource {
                     @Mutate
@@ -64,7 +61,7 @@ class MutationRuleApplicationOrderIntegrationTest extends AbstractIntegrationSpe
                     }
 
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks, MutationRecorder recorderInput) {
+                    void addTasks(ModelMap<Task> tasks, MutationRecorder recorderInput) {
                         tasks.create("echo", EchoTask) {
                             recorder = recorderInput
                         }
@@ -83,71 +80,101 @@ class MutationRuleApplicationOrderIntegrationTest extends AbstractIntegrationSpe
         output.contains "mutations: a, b"
     }
 
-    def "mutation rules are executed in the order of application for rule sources and order of declaration for dsl defined rules"() {
+    def "mutation rules are executed in a fixed and arbitrary order"() {
         when:
         EnableModelDsl.enable(executer)
         buildFile << '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class FirstSource extends RuleSource {
                 @Mutate
-                void first(MutationRecorder recorder, @Path("firstInput") String input) {
+                void first(MutationRecorder recorder) {
                     recorder.mutations << "first source"
                 }
             }
 
-
             class SecondSource extends RuleSource {
                 @Model
                 MutationRecorder recorder() {
                     new MutationRecorder()
                 }
 
-                @Model
-                String secondInput() {
-                    ""
+                @Mutate
+                void second(@Path("recorder") MutationRecorder recorder) {
+                    recorder.mutations << "second source"
                 }
 
                 @Mutate
-                void second(MutationRecorder recorder, @Path("secondInput") String input) {
-                    recorder.mutations << "second source"
+                void third(MutationRecorder recorder) {
+                    recorder.mutations << "third source"
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks, MutationRecorder recorderInput) {
+                void addTasks(ModelMap<Task> tasks, MutationRecorder recorderInput) {
                     tasks.create("echo", EchoTask) {
                         recorder = recorderInput
                     }
                 }
             }
 
-            class FirstInputProvider extends RuleSource {
+            apply type: FirstSource
+            model {
+                recorder {
+                    mutations << "first dsl"
+                }
+            }
+            apply type: SecondSource
+            model {
+                recorder {
+                    mutations << "second dsl"
+                }
+            }
+        '''
+
+        then:
+        succeeds "echo"
+
+        and:
+        output.contains "mutations: first source, second source, third source, first dsl, second dsl"
+    }
+
+    def "DSL rules are executed in order declared"() {
+        when:
+        EnableModelDsl.enable(executer)
+        buildFile << '''
+            class FirstSource extends RuleSource {
                 @Model
-                String firstInput() {
-                    ""
+                MutationRecorder recorder() {
+                    new MutationRecorder()
                 }
             }
 
-            apply type: FirstSource
             model {
+                tasks {
+                    echo(EchoTask) {
+                        recorder = $('recorder')
+                    }
+                }
                 recorder {
-                    $("firstInput")
                     mutations << "first dsl"
                 }
                 recorder {
-                    $("secondInput")
                     mutations << "second dsl"
                 }
             }
-            apply type: SecondSource
-            apply type: FirstInputProvider
+            apply type: FirstSource
+            model {
+                recorder {
+                    mutations << "third dsl"
+                }
+                recorder {
+                    mutations << "fourth dsl"
+                }
+            }
         '''
 
         then:
         succeeds "echo"
 
         and:
-        output.contains "mutations: first source, first dsl, second dsl, second source"
+        output.contains "mutations: first dsl, second dsl, third dsl, fourth dsl"
     }
 }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/PluginRuleSourceIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/PluginRuleSourceIntegrationTest.groovy
index e483c54..db044c3 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/PluginRuleSourceIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/PluginRuleSourceIntegrationTest.groovy
@@ -28,9 +28,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "plugin class can expose model rules"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -39,7 +36,7 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks, List<String> strings) {
+                    void addTasks(ModelMap<Task> tasks, List<String> strings) {
                         tasks.create("value") {
                             it.doLast {
                                 println "value: $strings"
@@ -69,8 +66,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "configuration in script is not executed if not needed"() {
         given:
         buildScript '''
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -98,8 +93,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "informative error message when rules are invalid"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
                 class Rules extends RuleSource {
                 }
@@ -119,8 +112,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "informative error message when two plugins declare model at the same path"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -150,8 +141,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "informative error message when two plugins declare model at the same path and model is already created"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -192,8 +181,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "informative error message when creation rule throws"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -221,8 +208,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "informative error message when dsl mutation rule throws"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -253,8 +238,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "model creator must provide instance"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Model
@@ -283,9 +266,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "plugin applied by plugin can contribute rules"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyBasePlugin {
                 static class Rules extends RuleSource {
                     @Mutate
@@ -307,7 +287,7 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks, List<String> strings) {
+                    void addTasks(ModelMap<Task> tasks, List<String> strings) {
                         tasks.create("value") {
                             it.doLast {
                                 println "value: $strings"
@@ -330,9 +310,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "configuration made to a project extension during afterEvaluate() is visible to rule sources"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyExtension {
                 String value = "original"
             }
@@ -354,7 +331,7 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks, String value) {
+                    void addTasks(ModelMap<Task> tasks, String value) {
                         tasks.create("value") {
                             it.doLast {
                                 println "value: $value"
@@ -381,13 +358,10 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "rule can depend on a concrete task type"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks, @Path("tasks.injected") Exec execTask) {
+                    void addTasks(ModelMap<Task> tasks, @Path("tasks.injected") Exec execTask) {
                         tasks.create("name") {
                             it.doLast {
                                 println "name: ${execTask.name}"
@@ -412,8 +386,6 @@ class PluginRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "plugin application fails if rule source constructor throws exception"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class Rules extends RuleSource {
                 Rules() {
                     throw new RuntimeException("failing constructor")
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ScopedRuleSourceIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ScopedRuleSourceIntegrationTest.groovy
index 92f9c6f..381faa6 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/ScopedRuleSourceIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/ScopedRuleSourceIntegrationTest.groovy
@@ -23,9 +23,6 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "rule source can be applied in scope of a collection builder element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MessageTask extends DefaultTask {
                 String message = "default"
 
@@ -49,7 +46,7 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("echo", MessageTask)
                     tasks.named("echo", EchoRules)
                 }
@@ -68,9 +65,6 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "scoped rule execution failure yields useful error message"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class ThrowingRule extends RuleSource {
                 @Mutate
                 void badRule(Task echo) {
@@ -80,7 +74,7 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
 
             class Rules extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.named("taskWithThrowingRuleApplied", ThrowingRule)
                     tasks.create("taskWithThrowingRuleApplied")
                 }
@@ -100,9 +94,6 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
     def "invalid rule definitions of scoped rules are reported with a message helping to identify the faulty rule"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class InvalidRuleSource extends RuleSource {
                 @Mutate
                 String invalidRule(Task echo) {
@@ -111,7 +102,7 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
 
             class Rules extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.named("taskWithInvalidRuleSourceApplied", InvalidRuleSource)
                     tasks.create("taskWithInvalidRuleSourceApplied")
                 }
@@ -124,16 +115,13 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
         fails "tasks"
 
         and:
-        failure.assertHasCause("Exception thrown while executing model rule: Rules#addTasks(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>)")
+        failure.assertHasCause("Exception thrown while executing model rule: Rules#addTasks(org.gradle.model.ModelMap<org.gradle.api.Task>)")
         failure.assertHasCause("InvalidRuleSource#invalidRule(org.gradle.api.Task) is not a valid model rule method")
     }
 
     def "unbound inputs of scoped rules are reported and their scope is shown"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class UnboundRuleSource extends RuleSource {
                 @Mutate
                 void unboundRule(String string, Integer integer, @Path("some.inner.path") String withInnerPath) {
@@ -142,7 +130,7 @@ class ScopedRuleSourceIntegrationTest extends AbstractIntegrationSpec {
 
             class Rules extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.named("taskWithUnboundRuleSourceApplied", UnboundRuleSource)
                     tasks.create("taskWithUnboundRuleSourceApplied")
                 }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/TaskCreationIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/TaskCreationIntegrationTest.groovy
index f4d9254..138e4b7 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/TaskCreationIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/TaskCreationIntegrationTest.groovy
@@ -22,9 +22,6 @@ import org.gradle.util.TextUtil
 class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
     def setup() {
         buildFile << """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MessageTask extends DefaultTask {
                 String message = "default"
 
@@ -50,7 +47,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks, MyModel myModel) {
+                void addTasks(ModelMap<Task> tasks, MyModel myModel) {
                     myModel.tasks.each { n ->
                         tasks.create(n) {
                           description = "task \$n"
@@ -118,7 +115,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks, MyMessage myMessage) {
+                void addTasks(ModelMap<Task> tasks, MyMessage myMessage) {
                     ['foo', 'bar'].each { n ->
                         tasks.create(n, MessageTask) {
                             message = "\${myMessage.message} \${name}: "
@@ -181,7 +178,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<MessageTask> tasks, MyMessage myMessage) {
+                void addTasks(ModelMap<MessageTask> tasks, MyMessage myMessage) {
                     tasks.create('bar') {
                         message += myMessage.message
                     }
@@ -218,7 +215,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     ['foo', 'bar'].each { n ->
                         tasks.create(n, MessageTask)
                     }
@@ -236,7 +233,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
         failure.assertHasCause('task is invalid!')
     }
 
-    def "can use CollectionBuilder API from a method rule to apply rules to tasks"() {
+    def "can use ModelMap API from a method rule to apply rules to tasks"() {
         given:
         buildFile << """
             class MyMessage {
@@ -250,7 +247,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTasks(CollectionBuilder<MessageTask> tasks) {
+                void addTasks(ModelMap<MessageTask> tasks) {
                     ['foo', 'bar'].each { n ->
                         tasks.create(n, MessageTask) {
                             message = "\$message \$name"
@@ -259,7 +256,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Defaults
-                void applyMessages(CollectionBuilder<MessageTask> tasks, MyMessage myMessage) {
+                void applyMessages(ModelMap<MessageTask> tasks, MyMessage myMessage) {
                     tasks.beforeEach {
                         message = myMessage.message
                     }
@@ -272,7 +269,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void cleanupMessages(CollectionBuilder<MessageTask> tasks) {
+                void cleanupMessages(ModelMap<MessageTask> tasks) {
                     tasks.named('bar') {
                         message = "[\$message]"
                     }
@@ -301,7 +298,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<MessageTask> tasks) {
+                void addTasks(ModelMap<MessageTask> tasks) {
                     ['foo', 'bar'].each { n ->
                         tasks.create(n, MessageTask) {
                             message = "\$message \$name"
@@ -342,7 +339,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void applyMessages(CollectionBuilder<MessageTask> tasks) {
+                void applyMessages(ModelMap<MessageTask> tasks) {
                     tasks.afterEach {
                         message += " message!"
                     }
@@ -368,7 +365,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<MessageTask> tasks) {
+                void addTasks(ModelMap<MessageTask> tasks) {
                     tasks.create("foo") {
                         message = "foo message"
                     }
@@ -394,7 +391,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("foo")
                     tasks.create("bar")
                 }
@@ -425,7 +422,7 @@ class TaskCreationIntegrationTest extends AbstractIntegrationSpec {
 
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<SomeTask> tasks) {
+                void addTasks(ModelMap<SomeTask> tasks) {
                     tasks.create("foo") {
                         println "\$name configured"
                     }
@@ -470,7 +467,7 @@ foo configured
                 }
 
                 @Mutate
-                void addTasks1(CollectionBuilder<Task> tasks, MyModel myModel) {
+                void addTasks1(ModelMap<Task> tasks, MyModel myModel) {
                     myModel.tasks.each { n ->
                         tasks.create(n) {
                           description = "task \$n"
@@ -479,7 +476,7 @@ foo configured
                 }
 
                 @Mutate
-                void addTasks2(CollectionBuilder<Task> tasks, MyModel myModel) {
+                void addTasks2(ModelMap<Task> tasks, MyModel myModel) {
                     myModel.tasks.each { n ->
                         tasks.create(n) {
                           description = "task \$n"
@@ -501,8 +498,8 @@ foo configured
         fails "tasks"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks2(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>, MyModel)")
-        failure.assertHasCause("Cannot create 'tasks.a' using creation rule 'MyPlugin#addTasks2(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>, MyModel) > create(a)' as the rule 'MyPlugin#addTasks1(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>, MyModel) > create(a)' is already registered to create this model element.")
+        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks2(org.gradle.model.ModelMap<org.gradle.api.Task>, MyModel)")
+        failure.assertHasCause("Cannot create 'tasks.a' using creation rule 'MyPlugin#addTasks2(org.gradle.model.ModelMap<org.gradle.api.Task>, MyModel) > create(a)' as the rule 'MyPlugin#addTasks1(org.gradle.model.ModelMap<org.gradle.api.Task>, MyModel) > create(a)' is already registered to create this model element.")
     }
 
     def "cannot create tasks during config of task"() {
@@ -510,7 +507,7 @@ foo configured
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("foo") {
                       tasks.create("bar")
                     }
@@ -524,8 +521,8 @@ foo configured
         fails "tasks"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>) > create(foo)")
-        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>' given to rule 'MyPlugin#addTasks(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>)'")
+        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.ModelMap<org.gradle.api.Task>) > foo.<init>")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.ModelMap<org.gradle.api.Task>' given to rule 'MyPlugin#addTasks(org.gradle.model.ModelMap<org.gradle.api.Task>)'")
     }
 
     def "failure during task instantiation is reasonably reported"() {
@@ -539,7 +536,7 @@ foo configured
 
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("foo", Faulty)
                 }
             }
@@ -551,7 +548,7 @@ foo configured
         fails "tasks"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>)")
+        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.ModelMap<org.gradle.api.Task>)")
         failure.assertHasCause("Could not create task of type 'Faulty'")
     }
 
@@ -560,7 +557,7 @@ foo configured
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("foo") {
                         throw new RuntimeException("config failure")
                     }
@@ -574,7 +571,7 @@ foo configured
         fails "tasks"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>)")
+        failure.assertHasCause("Exception thrown while executing model rule: MyPlugin#addTasks(org.gradle.model.ModelMap<org.gradle.api.Task>)")
         failure.assertHasCause("config failure")
     }
 
@@ -583,7 +580,7 @@ foo configured
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTasks(CollectionBuilder<Task> tasks) {
+                void addTasks(ModelMap<Task> tasks) {
                     tasks.create("foo")
                 }
             }
@@ -603,7 +600,7 @@ foo configured
         then:
         failure.assertHasCause("Exception thrown while executing model rule: model.tasks.foo")
         failure.assertHasCause("config failure")
-        failure.assertHasLineNumber(25)
+        failure.assertHasLineNumber(22)
     }
 
     def "task created in afterEvaluate() is visible to rules"() {
@@ -644,7 +641,7 @@ foo configured
         buildFile << """
             class MyPlugin extends RuleSource {
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks) {
+                void addTask(ModelMap<Task> tasks) {
                     tasks.create("foo")
                 }
             }
@@ -658,7 +655,7 @@ foo configured
         fails "foo"
 
         and:
-        failure.assertHasCause("Cannot create 'tasks.foo' using creation rule 'MyPlugin#addTask(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>) > create(foo)' as the rule 'Project.<init>.tasks.foo()' is already registered to create this model element.")
+        failure.assertHasCause("Cannot create 'tasks.foo' using creation rule 'MyPlugin#addTask(org.gradle.model.ModelMap<org.gradle.api.Task>) > create(foo)' as the rule 'Project.<init>.tasks.foo()' is already registered to create this model element.")
     }
 
     def "can create task with invalid model space name"() {
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/AbstractClassBackedManagedTypeIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/AbstractClassBackedManagedTypeIntegrationTest.groovy
index da414c9..541baba 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/AbstractClassBackedManagedTypeIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/AbstractClassBackedManagedTypeIntegrationTest.groovy
@@ -23,9 +23,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
     def "rule can provide a managed model element backed by an abstract class"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class Person {
                 abstract String getName()
@@ -39,7 +36,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $person.name"
@@ -61,9 +58,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
     def "managed type implemented as abstract class can have generative getters"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class Person {
                 abstract String getFirstName()
@@ -84,7 +78,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $person.name"
@@ -106,9 +100,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
     def "managed type implemented as abstract class can have a custom toString() implementation"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class CustomToString {
                 abstract String getStringRepresentation()
@@ -126,7 +117,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, CustomToString element) {
+                void addEchoTask(ModelMap<Task> tasks, CustomToString element) {
                     tasks.create("echo") {
                         it.doLast {
                             println "element: ${element.toString()}"
@@ -148,9 +139,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
     def "calling setters from custom toString() implementation is not allowed"() {
         when:
         buildFile << '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class CustomToStringCallingSetter {
                 abstract String getStringRepresentation()
@@ -167,7 +155,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, CustomToStringCallingSetter element) {
+                void addEchoTask(ModelMap<Task> tasks, CustomToStringCallingSetter element) {
                     tasks.create("echo") {
                         it.doLast {
                             println "element: ${element.toString()}"
@@ -202,10 +190,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
 
     def "calling setters from non-abstract getters is not allowed"() {
         when:
-        buildFile << '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-        '''
         defineCallsSetterInNonAbstractGetterClass()
         buildFile << '''
             class RulePlugin extends RuleSource {
@@ -214,7 +198,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void accessInvalidGenerativeProperty(CollectionBuilder<Task> tasks, CallsSetterInNonAbstractGetter element) {
+                void accessInvalidGenerativeProperty(ModelMap<Task> tasks, CallsSetterInNonAbstractGetter element) {
                     element.invalidGenerativeProperty
                 }
             }
@@ -231,10 +215,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
 
     def "calling setters of super class from non-abstract getters is not allowed"() {
         when:
-        buildFile << '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-        '''
         defineCallsSetterInNonAbstractGetterClass()
         buildFile << '''
             @Managed
@@ -255,7 +235,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void accessInvalidGenerativeProperty(CollectionBuilder<Task> tasks, CallsSuperGetterInNonAbstractGetter element) {
+                void accessInvalidGenerativeProperty(ModelMap<Task> tasks, CallsSuperGetterInNonAbstractGetter element) {
                     element.invalidGenerativeProperty
                 }
             }
@@ -273,9 +253,6 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
     def "reports managed abstract type in missing property error message"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class Person {
                 abstract String getName()
@@ -288,7 +265,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
                 }
 
                 @Mutate
-                void tasks(CollectionBuilder<Task> tasks, Person person) {
+                void tasks(ModelMap<Task> tasks, Person person) {
                     println person.unknown
                 }
             }
@@ -301,7 +278,7 @@ class AbstractClassBackedManagedTypeIntegrationTest extends AbstractIntegrationS
 
         and:
         failure.assertHasFileName("Build file '$buildFile'")
-        failure.assertHasLineNumber(18)
+        failure.assertHasLineNumber(15)
         failure.assertHasCause("No such property: unknown for class: Person")
     }
 
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ComplexManagedTypeIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ComplexManagedTypeIntegrationTest.groovy
index 6040b59..2cc1eaa 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ComplexManagedTypeIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ComplexManagedTypeIntegrationTest.groovy
@@ -23,9 +23,6 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "rule can provide a composite managed model element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 String getDisplayName()
@@ -70,7 +67,7 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Platform platform) {
+                void addPersonTask(ModelMap<Task> tasks, Platform platform) {
                     tasks.create("echo") {
                         it.doLast {
                             println "platform: $platform"
@@ -98,9 +95,6 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "rule can apply defaults to a nested managed model element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 String getDisplayName()
@@ -133,7 +127,7 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Platform platform) {
+                void addPersonTask(ModelMap<Task> tasks, Platform platform) {
                     tasks.create("echo") {
                         it.doLast {
                             println "platform: $platform.operatingSystem.name"
@@ -155,9 +149,6 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "rule can provide a managed model element that references another managed model element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 String getDisplayName()
@@ -203,7 +194,7 @@ class ComplexManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Platform platform) {
+                void addPersonTask(ModelMap<Task> tasks, Platform platform) {
                     tasks.create("echo") {
                         it.doLast {
                             println "platform: $platform"
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/CyclicalManagedTypeIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/CyclicalManagedTypeIntegrationTest.groovy
index 44e8b88..a5d22be 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/CyclicalManagedTypeIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/CyclicalManagedTypeIntegrationTest.groovy
@@ -23,9 +23,6 @@ class CyclicalManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "managed types can have cyclical managed type references"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Parent {
                 String getName()
@@ -48,7 +45,7 @@ class CyclicalManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, Parent parent) {
+                void addEchoTask(ModelMap<Task> tasks, Parent parent) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $parent.child.parent.name"
@@ -70,9 +67,6 @@ class CyclicalManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "managed types can have cyclical managed type references where more than two types constitute the cycle"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface A {
                 String getName()
@@ -100,7 +94,7 @@ class CyclicalManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, A a) {
+                void addEchoTask(ModelMap<Task> tasks, A a) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $a.b.c.a.name"
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/EnumsInManagedModelIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/EnumsInManagedModelIntegrationTest.groovy
index 7978b4f..19563fa 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/EnumsInManagedModelIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/EnumsInManagedModelIntegrationTest.groovy
@@ -28,9 +28,6 @@ class EnumsInManagedModelIntegrationTest extends AbstractIntegrationSpec {
     def "can use enums in managed model elements"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             enum Gender {
                 FEMALE, MALE, OTHER
             }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InterfaceBackedManagedTypeIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InterfaceBackedManagedTypeIntegrationTest.groovy
index d110741..14fb393 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InterfaceBackedManagedTypeIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InterfaceBackedManagedTypeIntegrationTest.groovy
@@ -25,9 +25,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
     def "rule method can define a managed model element backed by an interface"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -57,7 +54,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, Person person) {
+                void addEchoTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "person: $person"
@@ -81,9 +78,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
     def "rule method can apply defaults to a managed model element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -121,7 +115,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, Person person) {
+                void addEchoTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $person.name"
@@ -146,7 +140,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
 
             @Managed
             interface Person {
@@ -168,7 +161,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo", task -> {
                         task.doLast(unused -> {
                             System.out.println(String.format("name: %s", person.getName()));
@@ -195,7 +188,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
 
             @Managed
             interface Person {
@@ -214,7 +206,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("accessGenerativeName", task -> {
                         task.doLast(unused -> {
                             person.getName();
@@ -241,7 +233,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
 
             @Managed
             interface Person {
@@ -256,7 +247,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void linkPersonToTasks(CollectionBuilder<Task> tasks, Person person) {
+                void linkPersonToTasks(ModelMap<Task> tasks, Person person) {
                 }
             }
         '''
@@ -278,7 +269,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
 
             @Managed
             interface Person {
@@ -292,7 +282,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void linkPersonToTasks(CollectionBuilder<Task> tasks, Person person) {
+                void linkPersonToTasks(ModelMap<Task> tasks, Person person) {
                 }
             }
         '''
@@ -311,9 +301,6 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
     def "reports managed interface type in missing property error message"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -326,7 +313,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void tasks(CollectionBuilder<Task> tasks, Person person) {
+                void tasks(ModelMap<Task> tasks, Person person) {
                     println person.unknown
                 }
             }
@@ -339,7 +326,7 @@ class InterfaceBackedManagedTypeIntegrationTest extends AbstractIntegrationSpec
 
         and:
         failure.assertHasFileName("Build file '$buildFile'")
-        failure.assertHasLineNumber(18)
+        failure.assertHasLineNumber(15)
         failure.assertHasCause("No such property: unknown for class: Person")
     }
 
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelMutationIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelMutationIntegrationTest.groovy
index 9838e8a..7fb65de 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelMutationIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelMutationIntegrationTest.groovy
@@ -28,9 +28,6 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
     def "mutating managed inputs of a rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -48,7 +45,7 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addDependencyOnName(CollectionBuilder<Task> tasks, String name) {
+                void addDependencyOnName(ModelMap<Task> tasks, String name) {
                 }
             }
 
@@ -66,9 +63,6 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
     def "mutating composite managed inputs of a rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Pet {
                 String getName()
@@ -86,7 +80,7 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void tryToModifyCompositeSubjectOfAnotherRule(CollectionBuilder<Task> tasks, Person person) {
+                void tryToModifyCompositeSubjectOfAnotherRule(ModelMap<Task> tasks, Person person) {
                     person.pet.name = "foo"
                 }
             }
@@ -99,15 +93,12 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
 
         and:
         failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#tryToModifyCompositeSubjectOfAnotherRule")
-        failure.assertHasCause("Attempt to mutate closed view of model of type 'Pet' given to rule 'RulePlugin#tryToModifyCompositeSubjectOfAnotherRule(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>, Person)'")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'Pet' given to rule 'RulePlugin#tryToModifyCompositeSubjectOfAnotherRule(org.gradle.model.ModelMap<org.gradle.api.Task>, Person)'")
     }
 
     def "mutating managed inputs of a dsl rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -140,9 +131,6 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
     def "mutating managed objects outside of a creation rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -160,7 +148,7 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void tryToModifyManagedObject(CollectionBuilder<Task> tasks, Person person) {
+                void tryToModifyManagedObject(ModelMap<Task> tasks, Person person) {
                     Holder.person.name = "foo"
                 }
             }
@@ -179,9 +167,6 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
     def "mutating composite managed objects outside of a creation rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Pet {
                 String getName()
@@ -204,7 +189,7 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void tryToModifyManagedObject(CollectionBuilder<Task> tasks, Person person) {
+                void tryToModifyManagedObject(ModelMap<Task> tasks, Person person) {
                     Holder.person.pet.name = "foo"
                 }
             }
@@ -223,9 +208,6 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
     def "mutating managed objects referenced by another managed object outside of a creation rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Pet {
                 String getName()
@@ -254,7 +236,7 @@ class InvalidManagedModelMutationIntegrationTest extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void tryToModifyManagedObject(CollectionBuilder<Task> tasks, Person person) {
+                void tryToModifyManagedObject(ModelMap<Task> tasks, Person person) {
                     Holder.person.pet.name = "foo"
                 }
             }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelRuleIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelRuleIntegrationTest.groovy
index 13f5f20..74cf6ab 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelRuleIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/InvalidManagedModelRuleIntegrationTest.groovy
@@ -24,9 +24,6 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
     def "provides a useful error message when setting an incompatible type on a managed instance in Groovy"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
@@ -40,7 +37,7 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
                 }
 
                 @Mutate
-                void addDependencyOnPerson(CollectionBuilder<Task> tasks, Person person) {
+                void addDependencyOnPerson(ModelMap<Task> tasks, Person person) {
                 }
             }
 
@@ -60,7 +57,6 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
             import java.lang.reflect.*;
 
             @Managed
@@ -77,7 +73,7 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
                 }
 
                 @Mutate
-                void addDependencyOnPerson(CollectionBuilder<Task> tasks, Person person) {
+                void addDependencyOnPerson(ModelMap<Task> tasks, Person person) {
                 }
             }
         '''
@@ -96,9 +92,6 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
     def "cannot assign a non-managed instance to a property of a managed type"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 OperatingSystem getOperatingSystem()
@@ -117,7 +110,7 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
                 }
 
                 @Mutate
-                void addDependencyOnPlatform(CollectionBuilder<Task> tasks, Platform platform) {
+                void addDependencyOnPlatform(ModelMap<Task> tasks, Platform platform) {
                 }
             }
 
@@ -141,11 +134,8 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
     }
 
     def "cannot use value type as subject of void model rule"() {
-        given:
         when:
         buildScript '''
-            import org.gradle.model.*
-
             class Rules extends RuleSource {
               @Model
               void s(String s) {}
@@ -164,9 +154,6 @@ class InvalidManagedModelRuleIntegrationTest extends AbstractIntegrationSpec{
     def "provides a useful error message when an invalid managed type is used in a rule"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
                 String getName()
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelMapIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelMapIntegrationTest.groovy
new file mode 100644
index 0000000..2d63ad8
--- /dev/null
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelMapIntegrationTest.groovy
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.managed
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
+
+class ManagedModelMapIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
+    def "rule can create a map interface backed managed model elements"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Thing extends Named {
+              void setValue(String value)
+              String getValue()
+            }
+
+            @Managed
+            interface Container {
+              ModelMap<Thing> getThings();
+            }
+
+            class Rules extends RuleSource {
+
+              @Model
+              void container(Container container) {
+                container.things.create("a") { value = "1" }
+                container.things.create("b") { value = "2" }
+              }
+
+              @Model
+              void things(ModelMap<Thing> things) {
+                things.create("a") { value = "1" }
+                things.create("b") { value = "2" }
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("print") {
+                  doLast {
+                    println "containerThings: ${$("container.things").values().collect { it.name + ":" + it.value }.sort().join(",")}"
+                    println "things: ${$("things").values().collect { it.name + ":" + it.value }.sort().join(",")}"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "print"
+
+        and:
+        output.contains "containerThings: a:1,b:2"
+        output.contains "things: a:1,b:2"
+    }
+
+    def "rule can create a managed collection of abstract class backed managed model elements"() {
+        when:
+        buildScript '''
+            @Managed
+            abstract class Thing implements Named {
+              abstract String getName()
+              abstract void setValue(String value)
+              abstract String getValue()
+            }
+
+            @Managed
+            interface Container {
+              ModelMap<Thing> getThings();
+            }
+
+            class Rules extends RuleSource {
+
+              @Model
+              void container(Container container) {
+                container.things.create("a") { value = "1" }
+                container.things.create("b") { value = "2" }
+              }
+
+              @Model
+              void things(ModelMap<Thing> things) {
+                things.create("a") { value = "1" }
+                things.create("b") { value = "2" }
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("print") {
+                  doLast {
+                    println "containerThings: ${$("container.things").values().collect { it.name + ":" + it.value }.sort().join(",")}"
+                    println "things: ${$("things").values().collect { it.name + ":" + it.value }.sort().join(",")}"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "print"
+
+        and:
+        output.contains "containerThings: a:1,b:2"
+        output.contains "things: a:1,b:2"
+    }
+
+    def "reports failure that occurs in collection item initializer"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person extends Named {
+              String getValue()
+              void setValue(String string)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelMap<Person> people) {
+                people.create("foo") {
+                    throw new RuntimeException("broken")
+                }
+              }
+
+              @Mutate
+              void tasks(ModelMap<Task> tasks, ModelMap<Person> people) { }
+            }
+
+            apply type: Rules
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasDescription('A problem occurred configuring root project')
+        failure.assertHasCause('Exception thrown while executing model rule: Rules#people(org.gradle.model.ModelMap<Person>) > foo.<init>')
+        failure.assertHasCause('broken')
+    }
+
+    def "cannot read when mutable"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person extends Named {
+              String getValue()
+              void setValue(String string)
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelMap<Person> people) {
+                    people.size()
+                }
+
+                @Mutate
+                void addDependencyOnPeople(ModelMap<Task> tasks, ModelMap<Person> people) {
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#people")
+        failure.assertHasCause("Attempt to read a write only view of model of type 'org.gradle.model.ModelMap<Person>' given to rule 'RulePlugin#people(org.gradle.model.ModelMap<Person>)'")
+    }
+
+    def "cannot mutate when used as an input"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person extends Named {
+              String getValue()
+              void setValue(String string)
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelMap<Person> people) {}
+
+                @Mutate
+                void mutate(ModelMap<Task> tasks, ModelMap<Person> people) {
+                    people.create("foo") {}
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#mutate")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.ModelMap<Person>' given to rule 'RulePlugin#mutate(org.gradle.model.ModelMap<org.gradle.api.Task>, org.gradle.model.ModelMap<Person>)'")
+    }
+
+    def "can read children of map when used as input"() {
+        when:
+        buildScript """
+            @Managed
+            interface Parent {
+                String getName();
+                void setName(String string)
+
+                ModelMap<Child> getChildren();
+            }
+
+            @Managed
+            interface Child extends Named {
+                ModelSet<GrandChild> getChildren();
+            }
+
+            @Managed
+            interface GrandChild {
+                String getName();
+                void setName(String string)
+            }
+
+            class Rules extends RuleSource {
+                @Model
+                void parent(Parent p) {
+                }
+
+                @Mutate
+                void printParentTask(TaskContainer tasks, Parent p) {
+                    tasks.create("printParent") {
+                        it.doLast {
+                            println p.name
+                            for (Child c : p.children.values()) {
+                                println "  :" + c?.name
+                                for (GrandChild gc : c.children) {
+                                    println "    :" + gc?.name
+                                }
+                            }
+                        }
+                    }
+                }
+
+                @Mutate
+                void addChildren(@Path("parent.children") children) {
+                    children.create("c1") {
+                        it.children.create { gc ->
+                            gc.name = "gc1"
+                        }
+                    }
+                }
+            }
+
+            apply type: Rules
+
+            model {
+                parent {
+                    name = "parent"
+                }
+            }
+        """
+
+        then:
+        succeeds "printParent"
+        outputContains("""
+parent
+  :c1
+    :gc1
+""".trim()
+        )
+    }
+
+    def "name is not populated when entity is not named"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Thing {
+              String getName()
+              void setName(String name)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void things(ModelMap<Thing> things) {
+                things.create("a")
+                things.create("b")
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("print") {
+                  doLast {
+                    println "things: ${$("things").values().collect { it.name }.join(',')}"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "print"
+
+        and:
+        output.contains "things: null,null"
+    }
+
+}
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelPropertyTargetingRuleIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelPropertyTargetingRuleIntegrationTest.groovy
index c8fc597..32c38a5 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelPropertyTargetingRuleIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedModelPropertyTargetingRuleIntegrationTest.groovy
@@ -28,9 +28,6 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
     def "rule can target structured property of managed element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 OperatingSystem getOperatingSystem()
@@ -49,7 +46,7 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
+                void addTask(ModelMap<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
                   tasks.create("fromPlugin") {
                     doLast { println "fromPlugin: $os.name" }
                   }
@@ -78,9 +75,6 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
     def "rule can target structured property of managed element as subject"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 OperatingSystem getOperatingSystem()
@@ -102,7 +96,7 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
+                void addTask(ModelMap<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
                   tasks.create("fromPlugin") {
                     doLast { println "fromPlugin: $os.name" }
                   }
@@ -131,9 +125,6 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
     def "rule can target simple property of managed element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 String getName()
@@ -147,7 +138,7 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("platform.name") String name) {
+                void addTask(ModelMap<Task> tasks, @Path("platform.name") String name) {
                   tasks.create("fromPlugin") {
                     doLast { println "fromPlugin: $name" }
                   }
@@ -176,9 +167,6 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
     def "mutation rule can target property of managed element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 OperatingSystem getOperatingSystem()
@@ -197,7 +185,7 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("platform.operatingSystem.name") String name) {
+                void addTask(ModelMap<Task> tasks, @Path("platform.operatingSystem.name") String name) {
                   tasks.create("fromPlugin") {
                     doLast { println "fromPlugin: $name" }
                   }
@@ -226,9 +214,6 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
     def "creation rule can target property of managed element"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface OperatingSystem {
                 String getName()
@@ -247,7 +232,7 @@ class ManagedModelPropertyTargetingRuleIntegrationTest extends AbstractIntegrati
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("name") String name) {
+                void addTask(ModelMap<Task> tasks, @Path("name") String name) {
                   tasks.create("echo") {
                     doLast { println "name: $name" }
                   }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedSetIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedSetIntegrationTest.groovy
index 5e3addc..56e15fb 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedSetIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedSetIntegrationTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 the original author or authors.
+ * Copyright 2015 the original author 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,6 +20,10 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.util.TextUtil
 
+/**
+ * This whole test can be deleted with ManagedSet is removed.
+ * {@link ModelSetIntegrationTest} duplicates this for ModelSet.
+ */
 class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
 
     def setup() {
@@ -29,9 +33,6 @@ class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
     def "rule can create a managed collection of interface backed managed model elements"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
               String getName()
@@ -92,9 +93,6 @@ class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
     def "rule can create a managed collection of abstract class backed managed model elements"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class Person {
               abstract String getName()
@@ -133,9 +131,6 @@ class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
     def "managed model type has property of collection of managed types"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
               String getName()
@@ -186,9 +181,6 @@ class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
     def "managed model type can reference a collection of managed types"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
               String getName()
@@ -247,9 +239,6 @@ class ManagedSetIntegrationTest extends AbstractIntegrationSpec {
     def "rule method can apply defaults to a managed set"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
               String getName()
@@ -303,9 +292,6 @@ finalize
     def "creation and configuration of managed set elements is deferred until required"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             abstract class Person {
               Person() {
@@ -378,9 +364,6 @@ configure p3
     def "reports failure that occurs in collection item initializer"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
               String getName()
@@ -396,7 +379,7 @@ configure p3
               }
 
               @Mutate
-              void tasks(CollectionBuilder<Task> tasks, ManagedSet<Person> people) { }
+              void tasks(ModelMap<Task> tasks, ManagedSet<Person> people) { }
             }
 
             apply type: Rules
@@ -414,9 +397,6 @@ configure p3
     def "read methods of ManagedSet throw exceptions when used in a creation rule"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
             }
@@ -428,7 +408,7 @@ configure p3
                 }
 
                 @Mutate
-                void addDependencyOnPeople(CollectionBuilder<Task> tasks, ManagedSet<Person> people) {
+                void addDependencyOnPeople(ModelMap<Task> tasks, ManagedSet<Person> people) {
                 }
             }
 
@@ -446,9 +426,6 @@ configure p3
     def "read methods of ManagedSet throw exceptions when used in a mutation rule"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
             }
@@ -464,7 +441,7 @@ configure p3
                 }
 
                 @Mutate
-                void addDependencyOnPeople(CollectionBuilder<Task> tasks, ManagedSet<Person> people) {
+                void addDependencyOnPeople(ModelMap<Task> tasks, ManagedSet<Person> people) {
                 }
             }
 
@@ -482,9 +459,6 @@ configure p3
     def "mutating a managed set that is an input of a rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
             }
@@ -494,7 +468,7 @@ configure p3
                 void people(ManagedSet<Person> people) {}
 
                 @Mutate
-                void tryToMutateInputManagedSet(CollectionBuilder<Task> tasks, ManagedSet<Person> people) {
+                void tryToMutateInputManagedSet(ModelMap<Task> tasks, ManagedSet<Person> people) {
                     people.create {}
                 }
             }
@@ -507,15 +481,12 @@ configure p3
 
         and:
         failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#tryToMutateInputManagedSet")
-        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.collection.ManagedSet<Person>' given to rule 'RulePlugin#tryToMutateInputManagedSet(org.gradle.model.collection.CollectionBuilder<org.gradle.api.Task>, org.gradle.model.collection.ManagedSet<Person>)'")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.collection.ManagedSet<Person>' given to rule 'RulePlugin#tryToMutateInputManagedSet(org.gradle.model.ModelMap<org.gradle.api.Task>, org.gradle.model.collection.ManagedSet<Person>)'")
     }
 
     def "mutating a managed set outside of a creation rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
             }
@@ -531,7 +502,7 @@ configure p3
                 }
 
                 @Mutate
-                void tryToMutateManagedSetOutsideOfCreationRule(CollectionBuilder<Task> tasks, ManagedSet<Person> people) {
+                void tryToMutateManagedSetOutsideOfCreationRule(ModelMap<Task> tasks, ManagedSet<Person> people) {
                     Holder.people.create {}
                 }
             }
@@ -550,9 +521,6 @@ configure p3
     def "mutating managed set which is an input of a DSL rule is not allowed"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Person {
             }
@@ -579,4 +547,30 @@ configure p3
         failure.assertHasCause("Exception thrown while executing model rule: model.tasks")
         failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.collection.ManagedSet<Person>' given to rule 'model.tasks @ build file")
     }
+
+    def "cannot view managed set as model set"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ManagedSet<Person> people) {
+                }
+
+                @Mutate
+                void tasks(ModelMap<Task> tasks, ModelSet<Person> people) {}
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("The following model rules are unbound")
+    }
 }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeImplementationClassCachingSpec.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeImplementationClassCachingSpec.groovy
index 0bf68f1..169f191 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeImplementationClassCachingSpec.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeImplementationClassCachingSpec.groovy
@@ -23,9 +23,6 @@ class ManagedTypeImplementationClassCachingSpec extends AbstractIntegrationSpec
     def "managed type implementation class is generated once for each type and reused"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Named {
                 String getName()
@@ -42,7 +39,7 @@ class ManagedTypeImplementationClassCachingSpec extends AbstractIntegrationSpec
                 }
 
                 @Mutate
-                void addCompareImplementationClassesTask(CollectionBuilder<Task> tasks, @Path("first") Named first, @Path("second") Named second) {
+                void addCompareImplementationClassesTask(ModelMap<Task> tasks, @Path("first") Named first, @Path("second") Named second) {
                     tasks.create("compareImplementationClasses") {
                         it.doLast {
                             println "implementation class is reused: ${first.getClass().is(second.getClass())}"
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeWithUnmanagedPropertiesIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeWithUnmanagedPropertiesIntegrationTest.groovy
index e3a909d..01e8f1c 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeWithUnmanagedPropertiesIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ManagedTypeWithUnmanagedPropertiesIntegrationTest.groovy
@@ -28,9 +28,6 @@ class ManagedTypeWithUnmanagedPropertiesIntegrationTest extends AbstractIntegrat
     def "can have unmanaged property"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class UnmanagedThing {
               String value
             }
@@ -49,7 +46,7 @@ class ManagedTypeWithUnmanagedPropertiesIntegrationTest extends AbstractIntegrat
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, ManagedThing thing) {
+                void addTask(ModelMap<Task> tasks, ManagedThing thing) {
                     tasks.create("echo") {
                         it.doLast {
                             println "value: $thing.unmanaged.value"
@@ -71,9 +68,6 @@ class ManagedTypeWithUnmanagedPropertiesIntegrationTest extends AbstractIntegrat
     def "unmanaged property of managed type can be targeted by rules"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface Platform {
                 @Unmanaged
@@ -100,7 +94,7 @@ class ManagedTypeWithUnmanagedPropertiesIntegrationTest extends AbstractIntegrat
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
+                void addTask(ModelMap<Task> tasks, @Path("platform.operatingSystem") OperatingSystem os) {
                   tasks.create("fromPlugin") {
                     doLast { println "fromPlugin: $os.name" }
                   }
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ModelSetIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ModelSetIntegrationTest.groovy
new file mode 100644
index 0000000..972de8e
--- /dev/null
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/ModelSetIntegrationTest.groovy
@@ -0,0 +1,546 @@
+/*
+ * 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.model.managed
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
+import org.gradle.util.TextUtil
+
+class ModelSetIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
+    def "rule can create a managed collection of interface backed managed model elements"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+              String getName()
+              void setName(String string)
+            }
+
+            class Names {
+                List<String> names = []
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              Names names() {
+                return new Names(names: ["p1", "p2"])
+              }
+
+              @Model
+              void people(ModelSet<Person> people, Names names) {
+                names.names.each { n ->
+                    people.create { name = n }
+                }
+              }
+
+              @Mutate void addPeople(ModelSet<Person> people) {
+                people.create { name = "p3" }
+                people.create { name = "p4" }
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              people {
+                create { name = "p0" }
+              }
+
+              tasks {
+                create("printPeople") {
+                  doLast {
+                    def people = $("people")
+                    def names = people*.name.sort().join(", ")
+                    println "people: ${people.toString()}"
+                    println "names: $names"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printPeople"
+
+        and:
+        output.contains "people: org.gradle.model.ModelSet<Person> 'people'"
+        output.contains 'names: p0, p1, p2, p3, p4'
+    }
+
+    def "rule can create a managed collection of abstract class backed managed model elements"() {
+        when:
+        buildScript '''
+            @Managed
+            abstract class Person {
+              abstract String getName()
+              abstract void setName(String string)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelSet<Person> people) {
+                people.create { name = "p1" }
+                people.create { name = "p2" }
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("printPeople") {
+                  doLast {
+                    def names = $("people")*.name.sort().join(", ")
+                    println "people: $names"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printPeople"
+
+        and:
+        output.contains 'people: p1, p2'
+    }
+
+    def "managed model type has property of collection of managed types"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+              String getName()
+              void setName(String string)
+            }
+
+            @Managed
+            interface Group {
+              String getName()
+              void setName(String string)
+              ModelSet<Person> getMembers()
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void group(Group group) {
+                group.name = "Women in computing"
+
+                group.members.create { name = "Ada Lovelace" }
+                group.members.create { name = "Grace Hooper" }
+
+                assert group.members.is(group.members)
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("printGroup") {
+                  doLast {
+                    def members = $("group").members*.name.sort().join(", ")
+                    def name = $("group").name
+                    println "$name: $members"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printGroup"
+
+        and:
+        output.contains 'Women in computing: Ada Lovelace, Grace Hooper'
+    }
+
+    def "managed model type can reference a collection of managed types"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+              String getName()
+              void setName(String string)
+            }
+
+            @Managed
+            interface Group {
+              String getName()
+              void setName(String string)
+              ModelSet<Person> getMembers()
+              void setMembers(ModelSet<Person> members)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelSet<Person> people) {
+                people.create { name = "Ada Lovelace" }
+                people.create { name = "Grace Hooper" }
+              }
+
+              @Model
+              void group(Group group, @Path("people") ModelSet<Person> people) {
+                group.name = "Women in computing"
+
+                assert group.members == null
+
+                group.members = people
+
+                assert group.members.is(people)
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("printGroup") {
+                  doLast {
+                    def members = $("group").members*.name.sort().join(", ")
+                    def name = $("group").name
+                    println "$name: $members"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printGroup"
+
+        and:
+        output.contains 'Women in computing: Ada Lovelace, Grace Hooper'
+    }
+
+    def "rule method can apply defaults to a managed set"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+              String getName()
+              void setName(String string)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelSet<Person> people) {
+                println "initialize"
+              }
+
+              @Defaults void initialPeople(ModelSet<Person> people) {
+                println "apply defaults"
+              }
+
+              @Mutate void customPeople(ModelSet<Person> people) {
+                println "configure"
+              }
+
+              @Finalize void finalPeople(ModelSet<Person> people) {
+                println "finalize"
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              tasks {
+                create("printPeople") {
+                  doLast {
+                    def people = $("people")
+                    println "people: $people"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printPeople"
+
+        and:
+        output.contains TextUtil.toPlatformLineSeparators('''apply defaults
+initialize
+configure
+finalize
+''')
+    }
+
+    def "creation and configuration of managed set elements is deferred until required"() {
+        when:
+        buildScript '''
+            @Managed
+            abstract class Person {
+              Person() {
+                println "construct Person"
+              }
+              abstract String getName()
+              abstract void setName(String string)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelSet<Person> people) {
+                people.create {
+                    println "configure p1"
+                    name = "p1"
+                }
+                println "p1 defined"
+              }
+
+              @Mutate void addPeople(ModelSet<Person> people) {
+                people.create {
+                  println "configure p2"
+                  name = "p2"
+                }
+                println "p2 defined"
+              }
+            }
+
+            apply type: Rules
+
+            model {
+              people {
+                create {
+                  println "configure p3"
+                  name = "p3"
+                }
+                println "p3 defined"
+              }
+
+              tasks {
+                create("printPeople") {
+                  doLast {
+                    def names = $("people")*.name.sort().join(", ")
+                    println "people: $names"
+                  }
+                }
+              }
+            }
+        '''
+
+        then:
+        succeeds "printPeople"
+
+        and:
+        output.contains TextUtil.toPlatformLineSeparators('''
+p1 defined
+p2 defined
+p3 defined
+construct Person
+configure p1
+construct Person
+configure p2
+construct Person
+configure p3
+''')
+
+        output.contains "people: p1, p2, p3"
+    }
+
+    def "reports failure that occurs in collection item initializer"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+              String getName()
+              void setName(String string)
+            }
+
+            class Rules extends RuleSource {
+              @Model
+              void people(ModelSet<Person> people) {
+                people.create {
+                    throw new RuntimeException("broken")
+                }
+              }
+
+              @Mutate
+              void tasks(ModelMap<Task> tasks, ModelSet<Person> people) { }
+            }
+
+            apply type: Rules
+        '''
+
+        then:
+        fails "printPeople"
+
+        and:
+        failure.assertHasDescription('A problem occurred configuring root project')
+        failure.assertHasCause('Exception thrown while executing model rule: Rules#people(org.gradle.model.ModelSet<Person>)')
+        failure.assertHasCause('broken')
+    }
+
+    def "read methods of ModelSet throw exceptions when used in a creation rule"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelSet<Person> people) {
+                    people.size()
+                }
+
+                @Mutate
+                void addDependencyOnPeople(ModelMap<Task> tasks, ModelSet<Person> people) {
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#people")
+        failure.assertHasCause("Attempt to read a write only view of model of type 'org.gradle.model.ModelSet<Person>' given to rule 'RulePlugin#people(org.gradle.model.ModelSet<Person>)'")
+    }
+
+    def "read methods of ModelSet throw exceptions when used in a mutation rule"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelSet<Person> people) {
+                }
+
+                @Mutate
+                void readPeople(ModelSet<Person> people) {
+                    people.toList()
+                }
+
+                @Mutate
+                void addDependencyOnPeople(ModelMap<Task> tasks, ModelSet<Person> people) {
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#readPeople")
+        failure.assertHasCause("Attempt to read a write only view of model of type 'org.gradle.model.ModelSet<Person>' given to rule 'RulePlugin#readPeople(org.gradle.model.ModelSet<Person>)'")
+    }
+
+    def "mutating a managed set that is an input of a rule is not allowed"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelSet<Person> people) {}
+
+                @Mutate
+                void tryToMutateInputModelSet(ModelMap<Task> tasks, ModelSet<Person> people) {
+                    people.create {}
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#tryToMutateInputModelSet")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.ModelSet<Person>' given to rule 'RulePlugin#tryToMutateInputModelSet(org.gradle.model.ModelMap<org.gradle.api.Task>, org.gradle.model.ModelSet<Person>)'")
+    }
+
+    def "mutating a managed set outside of a creation rule is not allowed"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class Holder {
+                static ModelSet<Person> people
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelSet<Person> people) {
+                    Holder.people = people
+                }
+
+                @Mutate
+                void tryToMutateModelSetOutsideOfCreationRule(ModelMap<Task> tasks, ModelSet<Person> people) {
+                    Holder.people.create {}
+                }
+            }
+
+            apply type: RulePlugin
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: RulePlugin#tryToMutateModelSetOutsideOfCreationRule")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.ModelSet<Person>' given to rule 'RulePlugin#people(org.gradle.model.ModelSet<Person>)'")
+    }
+
+    def "mutating managed set which is an input of a DSL rule is not allowed"() {
+        when:
+        buildScript '''
+            @Managed
+            interface Person {
+            }
+
+            class RulePlugin extends RuleSource {
+                @Model
+                void people(ModelSet<Person> people) {
+                }
+            }
+
+            apply type: RulePlugin
+
+            model {
+                tasks {
+                    $("people").create {}
+                }
+            }
+        '''
+
+        then:
+        fails "tasks"
+
+        and:
+        failure.assertHasCause("Exception thrown while executing model rule: model.tasks")
+        failure.assertHasCause("Attempt to mutate closed view of model of type 'org.gradle.model.ModelSet<Person>' given to rule 'model.tasks @ build file")
+    }
+}
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PolymorphicManagedTypeIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PolymorphicManagedTypeIntegrationTest.groovy
index e9dbb26..a9a6dfc 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PolymorphicManagedTypeIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PolymorphicManagedTypeIntegrationTest.groovy
@@ -23,9 +23,6 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "rule can provide a managed model element backed by an abstract class that implements interfaces"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             interface Named {
                 String getName()
             }
@@ -42,7 +39,7 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $person.name"
@@ -64,9 +61,6 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "rule can provide a managed model element backed by an abstract class that extends other classes"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             abstract class Named {
                 abstract String getName()
             }
@@ -83,7 +77,7 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addPersonTask(CollectionBuilder<Task> tasks, Person person) {
+                void addPersonTask(ModelMap<Task> tasks, Person person) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $person.name"
@@ -105,9 +99,6 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "managed model interface can extend other interface"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             interface Named {
                 String getName()
                 void setName(String name)
@@ -127,7 +118,7 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, NamedThing namedThing) {
+                void addTask(ModelMap<Task> tasks, NamedThing namedThing) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $namedThing.name, value: $namedThing.value"
@@ -149,9 +140,6 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "can depend on managed super type as input and subject"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             interface Named {
                 String getName()
                 void setName(String name)
@@ -172,7 +160,7 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, Named named) {
+                void addTask(ModelMap<Task> tasks, Named named) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $named.name"
@@ -194,9 +182,6 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
     def "two managed types can extend the same parent"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             interface Named {
                 String getName()
                 void setName(String name)
@@ -228,7 +213,7 @@ class PolymorphicManagedTypeIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, NamedString string, NamedInteger integer) {
+                void addTask(ModelMap<Task> tasks, NamedString string, NamedInteger integer) {
                     tasks.create("echo") {
                         it.doLast {
                             println "name: $string.name, value: $string.value"
diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PrimitivesInManagedModelIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PrimitivesInManagedModelIntegrationTest.groovy
index cf21279..4abcba8 100644
--- a/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PrimitivesInManagedModelIntegrationTest.groovy
+++ b/subprojects/model-core/src/integTest/groovy/org/gradle/model/managed/PrimitivesInManagedModelIntegrationTest.groovy
@@ -23,9 +23,6 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
     def "values of primitive types and boxed primitive types are widened as usual when using groovy"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface PrimitiveTypes {
                 Long getLongPropertyFromInt()
@@ -43,7 +40,7 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, final PrimitiveTypes primitiveTypes) {
+                void addEchoTask(ModelMap<Task> tasks, final PrimitiveTypes primitiveTypes) {
                     tasks.create("echo") {
                         it.doLast {
                             println "from int: $primitiveTypes.longPropertyFromInt"
@@ -69,7 +66,6 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
         file('buildSrc/src/main/java/Rules.java') << '''
             import org.gradle.api.*;
             import org.gradle.model.*;
-            import org.gradle.model.collection.*;
 
             @Managed
             interface PrimitiveProperty {
@@ -93,7 +89,7 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, final PrimitiveProperty primitiveProperty) {
+                void addEchoTask(ModelMap<Task> tasks, final PrimitiveProperty primitiveProperty) {
                     tasks.create("echo", new Action<Task>() {
                         public void execute(Task task) {
                             task.doLast(new Action<Task>() {
@@ -127,9 +123,6 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
     def "can set/get properties of all supported unmanaged types"() {
         when:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             @Managed
             interface AllSupportedUnmanagedTypes {
                 Boolean getBooleanProperty()
@@ -159,6 +152,10 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
                 String getStringProperty()
 
                 void setStringProperty(String value)
+
+                File getFile()
+
+                void setFile(File file)
             }
 
             class RulePlugin extends RuleSource {
@@ -171,10 +168,11 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
                     element.bigIntegerProperty = new BigInteger("4")
                     element.bigDecimalProperty = new BigDecimal("5.5")
                     element.stringProperty = "test"
+                    element.file = new File('sample.txt')
                 }
 
                 @Mutate
-                void addEchoTask(CollectionBuilder<Task> tasks, AllSupportedUnmanagedTypes element) {
+                void addEchoTask(ModelMap<Task> tasks, AllSupportedUnmanagedTypes element) {
                     tasks.create("echo") {
                         it.doLast {
                             println "boolean: ${element.booleanProperty}"
@@ -184,6 +182,7 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
                             println "big integer: ${element.bigIntegerProperty}"
                             println "big decimal: ${element.bigDecimalProperty}"
                             println "string: ${element.stringProperty}"
+                            println "file: ${element.file}"
                         }
                     }
                 }
@@ -203,5 +202,30 @@ class PrimitivesInManagedModelIntegrationTest extends AbstractIntegrationSpec {
         output.contains "big integer: 4"
         output.contains "big decimal: 5.5"
         output.contains "string: test"
+        output.contains "file: sample.txt"
+    }
+
+    def "can specify managed models with file types"() {
+        when:
+        buildScript '''
+            @Managed
+            interface FileContainer {
+                File getFile()
+                void setFile(File file)
+            }
+
+            model {
+                gradleFileContainer(FileContainer) {
+                    file = file('sample.txt')
+                }
+
+                jdkFileContainer(FileContainer) {
+                    file = new File('sample.txt')
+                }
+            }
+        '''
+
+        then:
+        succeeds "model"
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/Defaults.java b/subprojects/model-core/src/main/java/org/gradle/model/Defaults.java
index 985482b..cc2f12c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/Defaults.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/Defaults.java
@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
 /**
  * Denotes that the {@link RuleSource} method rule carrying this annotation initializes the rule subject with default values.
  * <p>
- * Default rules execute after {@link Model} rules (i.e. after the element is created), but before {@link Mutate} rules.
+ * Default rules execute first for a given subject, just after the subject has been created but before {@link Model} rules and {@link Mutate} rules.
  * The first parameter of the rule is the rule subject, which is mutable for the duration of the rule.
  * <p>
  * Please see {@link RuleSource} for more information on method rules.
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/Managed.java b/subprojects/model-core/src/main/java/org/gradle/model/Managed.java
index 9504e22..69227ec 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/Managed.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/Managed.java
@@ -43,8 +43,8 @@ import java.lang.annotation.Target;
  * String getName();
  * </pre>
  * <p>
- * A getter and setter must be declared for each property that is not of a managed type or of {@link org.gradle.model.collection.ManagedSet}.
- * For properties of managed types or of {@link org.gradle.model.collection.ManagedSet} the getter is mandatory and the setter is optional.
+ * A getter and setter must be declared for each property that is not of a managed type or of {@link ModelSet}.
+ * For properties of managed types or of {@link ModelSet} the getter is mandatory and the setter is optional.
  * If no setter is provided the property is considered inherent and defaults to an "empty" instance of the type.
  *
  * <h4>Supported property types</h4>
@@ -59,17 +59,26 @@ import java.lang.annotation.Target;
  * <li>{@link Double}</li>
  * <li>{@link java.math.BigInteger}</li>
  * <li>{@link java.math.BigDecimal}</li>
+ * <li>{@link java.io.File}</li>
  * </ul>
  * <p>
  * All {@link Enum} types are also allowed.
  * <p>
  * Properties that are themselves of a managed type are also supported.
  * <p>
- * Currently, the only collection type that is supported is {@link org.gradle.model.collection.ManagedSet}.
+ * Currently, the only collection types that are supported are {@link ModelSet} and {@link ModelMap}.
  * <p>
  * Properties of any other type must have their getter annotated with {@link Unmanaged}.
  * An unmanaged property is not transparent to the model infrastructure and is guaranteed to be immutable when realized.
  *
+ * <h3>Named types</h3>
+ * <p>
+ * Managed types may implement/extend the {@link org.gradle.api.Named} interface.
+ * Any managed type implementing this interface will have its {@code name} attribute populated automatically
+ * based on the name of the corresponding node in the model graph.
+ * <p>
+ * The {@link ModelMap} type requires that its elements are {@link org.gradle.api.Named}.
+ *
  * <h3>Inheritance</h3>
  * <p>
  * Managed types can be arranged into an inheritance hierarchy.
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/ModelMap.java b/subprojects/model-core/src/main/java/org/gradle/model/ModelMap.java
new file mode 100644
index 0000000..90cd7f9
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/ModelMap.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.Nullable;
+import org.gradle.model.collection.CollectionBuilder;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Model backed map like structure allowing adding of items where instantiation is managed.
+ * <p>
+ * {@link org.gradle.model.Managed} types may declare model map properties.
+ * Model maps can only contain managed types.
+ *
+ * @param <T> the contract type for all items
+ */
+ at SuppressWarnings("deprecation")
+ at Incubating
+public interface ModelMap<T> extends CollectionBuilder<T> {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    <S> ModelMap<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    int size();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    boolean isEmpty();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nullable
+    @Override
+    T get(Object name);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nullable
+    @Override
+    T get(String name);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    boolean containsKey(Object name);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    boolean containsValue(Object item);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    Set<String> keySet();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void create(String name);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void create(String name, Action<? super T> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    <S extends T> void create(String name, Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    <S extends T> void create(String name, Class<S> type, Action<? super S> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void named(String name, Action<? super T> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void named(String name, Class<? extends RuleSource> ruleSource);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void beforeEach(Action<? super T> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    <S> void beforeEach(Class<S> type, Action<? super S> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    void all(Action<? super T> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    <S> void withType(Class<S> type, Action<? super S> configAction);
+
+    @Override
+    /**
+     * {@inheritDoc}
+     */
+    <S> void withType(Class<S> type, Class<? extends RuleSource> rules);
+
+    @Override
+    /**
+     * {@inheritDoc}
+     */
+    void afterEach(Action<? super T> configAction);
+
+    @Override
+    /**
+     * {@inheritDoc}
+     */
+    <S> void afterEach(Class<S> type, Action<? super S> configAction);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    Collection<T> values();
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/ModelSet.java b/subprojects/model-core/src/main/java/org/gradle/model/ModelSet.java
new file mode 100644
index 0000000..2269f25
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/ModelSet.java
@@ -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.model;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.model.collection.ManagedSet;
+
+/**
+ * A set of managed model objects.
+ * <p>
+ * {@link org.gradle.model.Managed} types may declare managed set properties.
+ * Managed sets can only contain managed types.
+ * <p>
+ * Managed set objects cannot be mutated via the mutative methods of the {@link java.util.Set} interface (e.g. {@link java.util.Set#add(Object)}, {@link java.util.Set#clear()}).
+ * To add elements to the set, the {@link #create(Action)} method can be used.
+ *
+ * @param <T> the type of model object
+ */
+ at Incubating
+public interface ModelSet<T> extends ManagedSet<T> {
+
+    /**
+     * Declares a new set element, configured by the given action.
+     *
+     * @param action the object configuration
+     */
+    void create(Action<? super T> action);
+
+    /**
+     * Apply the given action to each set element just after it is created.
+     * <p>
+     * The configuration action is equivalent in terms of lifecycle to {@link org.gradle.model.Defaults} rule methods.
+     *
+     * @param configAction the object configuration
+     */
+    void beforeEach(Action<? super T> configAction);
+
+    /**
+     * Apply the given action to each set element just before it is considered to be realised.
+     * <p>
+     * The configuration action is equivalent in terms of lifecycle to {@link org.gradle.model.Finalize} rule methods.
+     *
+     * @param configAction the object configuration
+     */
+    void afterEach(Action<? super T> configAction);
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/collection/CollectionBuilder.java b/subprojects/model-core/src/main/java/org/gradle/model/collection/CollectionBuilder.java
index ce23f83..294de99 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/collection/CollectionBuilder.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/collection/CollectionBuilder.java
@@ -17,18 +17,20 @@
 package org.gradle.model.collection;
 
 import org.gradle.api.Action;
-import org.gradle.api.Incubating;
 import org.gradle.api.Nullable;
 import org.gradle.model.RuleSource;
 
+import java.util.Collection;
 import java.util.Set;
 
 /**
  * Allows the adding of items to a named collection where instantiation is managed.
  *
  * @param <T> the contract type for all items
+ *
+ * @deprecated use {@link org.gradle.model.ModelMap} instead
  */
- at Incubating
+ at Deprecated
 public interface CollectionBuilder<T> {
     /**
      * Returns a collection containing the items from this collection which are of the specified type.
@@ -226,4 +228,11 @@ public interface CollectionBuilder<T> {
      * @param configAction An action that configures the item. The action is executed when the item is required.
      */
     <S> void afterEach(Class<S> type, Action<? super S> configAction);
+
+    /**
+     * Returns the items in this collection.
+     *
+     * @return The items.
+     */
+    Collection<T> values();
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/collection/ManagedSet.java b/subprojects/model-core/src/main/java/org/gradle/model/collection/ManagedSet.java
index c4d724e..02fba03 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/collection/ManagedSet.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/collection/ManagedSet.java
@@ -17,7 +17,6 @@
 package org.gradle.model.collection;
 
 import org.gradle.api.Action;
-import org.gradle.api.Incubating;
 
 import java.util.Set;
 
@@ -31,8 +30,9 @@ import java.util.Set;
  * To add elements to the set, the {@link #create(Action)} method can be used.
  *
  * @param <T> the type of model object
+ * @deprecated use {@link org.gradle.model.ModelSet} instead
  */
- at Incubating
+ at Deprecated
 public interface ManagedSet<T> extends Set<T> {
 
     /**
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/collection/internal/ModelMapModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/collection/internal/ModelMapModelProjection.java
new file mode 100644
index 0000000..3fb0875
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/collection/internal/ModelMapModelProjection.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.collection.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import org.gradle.api.Nullable;
+import org.gradle.model.ModelMap;
+import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.internal.core.*;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+public class ModelMapModelProjection<I> implements ModelProjection {
+
+    @SuppressWarnings("deprecation")
+    private final static Set<Class<?>> SUPPORTED_CONTAINER_TYPES = ImmutableSet.<Class<?>>of(ModelMap.class, CollectionBuilder.class);
+
+    public static <T> ModelProjection unmanaged(ModelType<T> itemType, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        return new ModelMapModelProjection<T>(itemType, false, false, creatorStrategy);
+    }
+
+    public static <T> ModelProjection unmanaged(Class<T> itemType, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        return unmanaged(ModelType.of(itemType), creatorStrategy);
+    }
+
+    public static <T> ModelProjection managed(ModelType<T> itemType, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        return new ModelMapModelProjection<T>(itemType, false, true, creatorStrategy);
+    }
+
+    protected final Class<I> baseItemType;
+    protected final ModelType<I> baseItemModelType;
+    private final boolean eager;
+    private final ChildNodeCreatorStrategy<? super I> creatorStrategy;
+    private final boolean managed;
+
+    protected ModelMapModelProjection(ModelType<I> baseItemModelType, boolean eager, boolean managed, ChildNodeCreatorStrategy<? super I> creatorStrategy) {
+        this.baseItemModelType = baseItemModelType;
+        this.eager = eager;
+        this.managed = managed;
+        this.baseItemType = baseItemModelType.getConcreteClass();
+        this.creatorStrategy = creatorStrategy;
+    }
+
+    protected Collection<? extends Class<?>> getCreatableTypes(MutableModelNode node) {
+        return Collections.singleton(baseItemType);
+    }
+
+    private String getContainerTypeDescription(Class<?> containerType, Collection<? extends Class<?>> creatableTypes) {
+        StringBuilder sb = new StringBuilder(containerType.getName());
+        if (creatableTypes.size() == 1) {
+            @SuppressWarnings("ConstantConditions")
+            String onlyType = Iterables.getFirst(creatableTypes, null).getName();
+            sb.append("<").append(onlyType).append(">");
+        } else {
+            sb.append("<T>; where T is one of [");
+            Joiner.on(", ").appendTo(sb, CollectionUtils.sort(Iterables.transform(creatableTypes, new Function<Class<?>, String>() {
+                public String apply(Class<?> input) {
+                    return input.getName();
+                }
+            })));
+            sb.append("]");
+        }
+        return sb.toString();
+    }
+
+    public Iterable<String> getReadableTypeDescriptions(MutableModelNode node) {
+        return getWritableTypeDescriptions(node);
+    }
+
+    protected Class<? extends I> itemType(ModelType<?> targetType) {
+        Class<?> targetClass = targetType.getRawClass();
+        if (SUPPORTED_CONTAINER_TYPES.contains(targetClass)) {
+            Class<?> targetItemClass = targetType.getTypeVariables().get(0).getRawClass();
+            if (targetItemClass.isAssignableFrom(baseItemType)) {
+                return baseItemType;
+            }
+            if (baseItemType.isAssignableFrom(targetItemClass)) {
+                return targetItemClass.asSubclass(baseItemType);
+            }
+            return null;
+        }
+        if (targetClass.isAssignableFrom(ModelMap.class)) {
+            return baseItemType;
+        }
+        return null;
+    }
+
+    public <T> boolean canBeViewedAsWritable(ModelType<T> targetType) {
+        return itemType(targetType) != null;
+    }
+
+    public <T> boolean canBeViewedAsReadOnly(ModelType<T> type) {
+        return canBeViewedAsWritable(type);
+    }
+
+    public <T> ModelView<? extends T> asReadOnly(ModelType<T> type, MutableModelNode modelNode, @Nullable ModelRuleDescriptor ruleDescriptor) {
+        return doAs(type, modelNode, ruleDescriptor, false);
+    }
+
+    public <T> ModelView<? extends T> asWritable(ModelType<T> targetType, MutableModelNode node, ModelRuleDescriptor ruleDescriptor, List<ModelView<?>> inputs) {
+        return doAs(targetType, node, ruleDescriptor, true);
+    }
+
+    @Nullable
+    private <T> ModelView<? extends T> doAs(ModelType<T> targetType, MutableModelNode node, ModelRuleDescriptor ruleDescriptor, boolean mutable) {
+        Class<? extends I> itemType = itemType(targetType);
+        if (itemType != null) {
+            return uncheckedCast(toView(targetType, ruleDescriptor, node, itemType, mutable, !managed || !mutable));
+        }
+        return null;
+    }
+
+    private <T, S extends I> ModelView<ModelMap<S>> toView(ModelType<T> targetType, ModelRuleDescriptor sourceDescriptor, MutableModelNode node, Class<S> itemClass, boolean mutable, boolean canReadChildren) {
+        DefaultModelViewState state = new DefaultModelViewState(targetType, sourceDescriptor, mutable, canReadChildren);
+        ModelType<S> itemType = ModelType.of(itemClass);
+        ModelMap<I> builder = new NodeBackedModelMap<I>(baseItemModelType, sourceDescriptor, node, eager, state, creatorStrategy);
+
+        return InstanceModelView.of(
+            node.getPath(),
+            ModelTypes.modelMap(itemType),
+            ModelMapGroovyDecorator.wrap(builder.withType(itemClass)),
+            state.closer()
+        );
+    }
+
+    @Override
+    public Iterable<String> getWritableTypeDescriptions(final MutableModelNode node) {
+        final Collection<? extends Class<?>> creatableTypes = getCreatableTypes(node);
+        return Iterables.transform(SUPPORTED_CONTAINER_TYPES, new Function<Class<?>, String>() {
+            public String apply(Class<?> containerType) {
+                return getContainerTypeDescription(containerType, creatableTypes);
+            }
+        });
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ModelMapModelProjection<?> that = (ModelMapModelProjection<?>) o;
+
+        return baseItemType.equals(that.baseItemType) && baseItemModelType.equals(that.baseItemModelType);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = baseItemType.hashCode();
+        result = 31 * result + baseItemModelType.hashCode();
+        return result;
+    }
+
+    @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ActionBackedModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ActionBackedModelAction.java
deleted file mode 100644
index a37c1af..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ActionBackedModelAction.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.model.internal.core;
-
-import org.gradle.api.Action;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-
-import java.util.Collections;
-import java.util.List;
-
-public class ActionBackedModelAction<T> implements ModelAction<T> {
-    private final ModelReference<T> subject;
-    private final Action<? super T> configAction;
-    private final ModelRuleDescriptor descriptor;
-
-    public ActionBackedModelAction(ModelReference<T> subject, ModelRuleDescriptor descriptor, Action<? super T> configAction) {
-        this.subject = subject;
-        this.configAction = configAction;
-        this.descriptor = descriptor;
-    }
-
-    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, Action<? super T> configAction) {
-        return new ActionBackedModelAction<T>(reference, descriptor, configAction);
-    }
-
-    @Override
-    public ModelReference<T> getSubject() {
-        return subject;
-    }
-
-    @Override
-    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
-        configAction.execute(object);
-    }
-
-    @Override
-    public List<ModelReference<?>> getInputs() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public ModelRuleDescriptor getDescriptor() {
-        return descriptor;
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BaseInstanceFactory.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BaseInstanceFactory.java
new file mode 100644
index 0000000..d4f64b1
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BaseInstanceFactory.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.gradle.api.GradleException;
+import org.gradle.api.Nullable;
+import org.gradle.internal.Cast;
+import org.gradle.internal.util.BiFunction;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class BaseInstanceFactory<T, P> implements InstanceFactory<T, P> {
+
+    private class Registration<S extends T> {
+        private final ModelRuleDescriptor source;
+        private final BiFunction<? extends S, ? super P, ? super MutableModelNode> factory;
+
+        public Registration(ModelRuleDescriptor source, BiFunction<? extends S, ? super P, ? super MutableModelNode> factory) {
+            this.source = source;
+            this.factory = factory;
+        }
+    }
+
+    private final String displayName;
+    private final Map<Class<?>, Registration<?>> factories = Maps.newIdentityHashMap();
+
+    public BaseInstanceFactory(String displayName) {
+        this.displayName = displayName;
+    }
+
+    @Override
+    public <S extends T> void register(Class<S> type, @Nullable ModelRuleDescriptor sourceRule, BiFunction<? extends S, ? super P, ? super MutableModelNode> factory) {
+        Registration<S> registration = getRegistration(type);
+        if (registration != null) {
+            StringBuilder builder = new StringBuilder("Cannot register a factory for type ")
+                .append(type.getSimpleName())
+                .append(" because a factory for this type was already registered");
+
+            if (registration.source != null) {
+                builder.append(" by ");
+                registration.source.describeTo(builder);
+            }
+            builder.append(".");
+            throw new GradleException(builder.toString());
+        }
+
+        factories.put(type, new Registration<S>(sourceRule, factory));
+    }
+
+    private <S extends T> Registration<S> getRegistration(Class<S> type) {
+        return Cast.uncheckedCast(factories.get(type));
+    }
+
+    @Override
+    public <S extends T> S create(Class<S> type, MutableModelNode modelNode, P payload) {
+        Registration<S> registration = getRegistration(type);
+        if (registration == null) {
+            throw new IllegalArgumentException(
+                String.format("Cannot create a %s because this type is not known to %s. Known types are: %s", type.getSimpleName(), displayName, getSupportedTypeNames()));
+        }
+        return registration.factory.apply(payload, modelNode);
+    }
+
+    @Override
+    public 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);
+    }
+
+    @Override
+    public String toString() {
+        return "[" + getSupportedTypeNames() + "]";
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BiActionBackedModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BiActionBackedModelAction.java
deleted file mode 100644
index a0816d4..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/BiActionBackedModelAction.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.model.internal.core;
-
-import org.gradle.internal.BiAction;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-
-import java.util.Collections;
-import java.util.List;
-
-public class BiActionBackedModelAction<T> implements ModelAction<T> {
-    private final ModelReference<T> modelReference;
-    private final ModelRuleDescriptor descriptor;
-    private final List<ModelReference<?>> inputs;
-    private final BiAction<? super T, ? super List<ModelView<?>>> initializer;
-
-    public BiActionBackedModelAction(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs, BiAction<? super T, ? super List<ModelView<?>>> initializer) {
-        this.modelReference = modelReference;
-        this.descriptor = descriptor;
-        this.inputs = inputs;
-        this.initializer = initializer;
-    }
-
-    public static <T> BiActionBackedModelAction<T> of(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs, BiAction<? super T, ? super List<ModelView<?>>> initializer) {
-        return new BiActionBackedModelAction<T>(modelReference, descriptor, inputs, initializer);
-    }
-
-    public static <T, I> BiActionBackedModelAction<T> single(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, final ModelReference<I> input, final BiAction<? super T, ? super I> initializer) {
-        return new BiActionBackedModelAction<T>(modelReference, descriptor, Collections.<ModelReference<?>>singletonList(input), new BiAction<T, List<ModelView<?>>>() {
-            @Override
-            public void execute(T t, List<ModelView<?>> modelViews) {
-                initializer.execute(t, ModelViews.assertType(modelViews.get(0), input.getType()).getInstance());
-            }
-        });
-    }
-
-    @Override
-    public ModelReference<T> getSubject() {
-        return modelReference;
-    }
-
-    @Override
-    public ModelRuleDescriptor getDescriptor() {
-        return descriptor;
-    }
-
-    @Override
-    public List<ModelReference<?>> getInputs() {
-        return inputs;
-    }
-
-    @Override
-    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
-        initializer.execute(object, inputs);
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChainingModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChainingModelProjection.java
index 297d817..776e0b3 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChainingModelProjection.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChainingModelProjection.java
@@ -16,8 +16,10 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
 import org.gradle.api.Nullable;
-import org.gradle.api.Transformer;
 import org.gradle.api.specs.Spec;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.type.ModelType;
@@ -48,22 +50,22 @@ public class ChainingModelProjection implements ModelProjection {
         });
     }
 
-    private Iterable<String> collectDescriptions(Transformer<Iterable<String>, ModelProjection> transformer) {
-        return CollectionUtils.flattenCollections(String.class, CollectionUtils.collect(projections, transformer));
+    private Iterable<String> collectDescriptions(final Function<ModelProjection, Iterable<String>> transformer) {
+        return Iterables.concat(Iterables.transform(projections, transformer));
     }
 
-    public Iterable<String> getWritableTypeDescriptions() {
-        return collectDescriptions(new Transformer<Iterable<String>, ModelProjection>() {
-            public Iterable<String> transform(ModelProjection projection) {
-                return projection.getWritableTypeDescriptions();
+    public Iterable<String> getWritableTypeDescriptions(final MutableModelNode node) {
+        return collectDescriptions(new Function<ModelProjection, Iterable<String>>() {
+            public Iterable<String> apply(ModelProjection projection) {
+                return projection.getWritableTypeDescriptions(node);
             }
         });
     }
 
-    public Iterable<String> getReadableTypeDescriptions() {
-        return collectDescriptions(new Transformer<Iterable<String>, ModelProjection>() {
-            public Iterable<String> transform(ModelProjection projection) {
-                return projection.getReadableTypeDescriptions();
+    public Iterable<String> getReadableTypeDescriptions(final MutableModelNode node) {
+        return collectDescriptions(new Function<ModelProjection, Iterable<String>>() {
+            public Iterable<String> apply(ModelProjection projection) {
+                return projection.getReadableTypeDescriptions(node);
             }
         });
     }
@@ -106,6 +108,11 @@ public class ChainingModelProjection implements ModelProjection {
     }
 
     @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
+
+    @Override
     public int hashCode() {
         return projections.hashCode();
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChildNodeCreatorStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChildNodeCreatorStrategy.java
new file mode 100644
index 0000000..124cd27
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ChildNodeCreatorStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+
+public interface ChildNodeCreatorStrategy<T> {
+
+    // Node must project item as S
+    <S extends T> ModelCreator creator(MutableModelNode parentNode, ModelRuleDescriptor sourceDescriptor, ModelType<S> type, String name);
+
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/CollectionBuilderModelView.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/CollectionBuilderModelView.java
deleted file mode 100644
index 7eb762f..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/CollectionBuilderModelView.java
+++ /dev/null
@@ -1,283 +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.model.internal.core;
-
-import groovy.lang.Closure;
-import groovy.lang.GroovyObjectSupport;
-import groovy.lang.MissingMethodException;
-import groovy.lang.MissingPropertyException;
-import net.jcip.annotations.NotThreadSafe;
-import org.gradle.api.Action;
-import org.gradle.api.Nullable;
-import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.model.ModelViewClosedException;
-import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Set;
-
-import static org.gradle.internal.Cast.uncheckedCast;
-
- at NotThreadSafe
-public class CollectionBuilderModelView<T> implements ModelView<CollectionBuilder<T>> {
-
-    private final ModelType<CollectionBuilder<T>> type;
-    private final CollectionBuilder<T> instance;
-    private final ModelRuleDescriptor ruleDescriptor;
-    private final ModelPath path;
-
-    private boolean closed;
-
-    public CollectionBuilderModelView(ModelPath path, ModelType<CollectionBuilder<T>> type, CollectionBuilder<T> rawInstance, ModelRuleDescriptor ruleDescriptor) {
-        this.path = path;
-        this.type = type;
-        this.ruleDescriptor = ruleDescriptor;
-        this.instance = new Decorator<T>(rawInstance);
-    }
-
-    @Override
-    public ModelPath getPath() {
-        return path;
-    }
-
-    public ModelType<CollectionBuilder<T>> getType() {
-        return type;
-    }
-
-    public CollectionBuilder<T> getInstance() {
-        return instance;
-    }
-
-    public void close() {
-        closed = true;
-    }
-
-    // TODO - mix in Groovy support and share with managed set
-    public class Decorator<I> extends GroovyObjectSupport implements CollectionBuilder<I> {
-        private final CollectionBuilder<I> rawInstance;
-
-        public Decorator(CollectionBuilder<I> rawInstance) {
-            this.rawInstance = rawInstance;
-        }
-
-        @Override
-        public String toString() {
-            return rawInstance.toString();
-        }
-
-        @Override
-        public <S> CollectionBuilder<S> withType(Class<S> type) {
-            return new Decorator<S>(rawInstance.withType(type));
-        }
-
-        @Override
-        public int size() {
-            return rawInstance.size();
-        }
-
-        @Override
-        public boolean isEmpty() {
-            return rawInstance.isEmpty();
-        }
-
-        @Nullable
-        @Override
-        public I get(String name) {
-            return rawInstance.get(name);
-        }
-
-        @Nullable
-        @Override
-        public I get(Object name) {
-            return rawInstance.get(name);
-        }
-
-        @Override
-        public boolean containsKey(Object name) {
-            return rawInstance.containsKey(name);
-        }
-
-        @Override
-        public boolean containsValue(Object item) {
-            return rawInstance.containsValue(item);
-        }
-
-        @Override
-        public Set<String> keySet() {
-            return rawInstance.keySet();
-        }
-
-        @Override
-        public void create(String name) {
-            assertNotClosed();
-            rawInstance.create(name);
-        }
-
-        @Override
-        public void create(String name, Action<? super I> configAction) {
-            assertNotClosed();
-            rawInstance.create(name, configAction);
-        }
-
-        @Override
-        public <S extends I> void create(String name, Class<S> type) {
-            assertNotClosed();
-            rawInstance.create(name, type);
-        }
-
-        @Override
-        public <S extends I> void create(String name, Class<S> type, Action<? super S> configAction) {
-            assertNotClosed();
-            rawInstance.create(name, type, configAction);
-        }
-
-        @Override
-        public void named(String name, Action<? super I> configAction) {
-            assertNotClosed();
-            rawInstance.named(name, configAction);
-        }
-
-        @Override
-        public void named(String name, Class<? extends RuleSource> ruleSource) {
-            assertNotClosed();
-            rawInstance.named(name, ruleSource);
-        }
-
-        @Override
-        public void beforeEach(Action<? super I> configAction) {
-            assertNotClosed();
-            rawInstance.beforeEach(configAction);
-        }
-
-        @Override
-        public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
-            assertNotClosed();
-            rawInstance.beforeEach(type, configAction);
-        }
-
-        @Override
-        public void all(Action<? super I> configAction) {
-            assertNotClosed();
-            rawInstance.all(configAction);
-        }
-
-        @Override
-        public <S> void withType(Class<S> type, Action<? super S> configAction) {
-            assertNotClosed();
-            rawInstance.withType(type, configAction);
-        }
-
-        @Override
-        public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
-            rawInstance.withType(type, rules);
-        }
-
-        @Override
-        public void afterEach(Action<? super I> configAction) {
-            assertNotClosed();
-            rawInstance.afterEach(configAction);
-        }
-
-        @Override
-        public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
-            assertNotClosed();
-            rawInstance.afterEach(type, configAction);
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public void create(String name, Closure<? super I> configAction) {
-            create(name, new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public <S extends I> void create(String name, Class<S> type, Closure<? super S> configAction) {
-            create(name, type, new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public void named(String name, Closure<? super I> configAction) {
-            named(name, new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public void all(Closure<? super I> configAction) {
-            all(new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public <S> void withType(Class<S> type, Closure<? super S> configAction) {
-            withType(type, new ClosureBackedAction<S>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public void beforeEach(Closure<? super I> configAction) {
-            beforeEach(new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public <S> void beforeEach(Class<S> type, Closure<? super S> configAction) {
-            beforeEach(type, new ClosureBackedAction<S>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public void afterEach(Closure<? super I> configAction) {
-            afterEach(new ClosureBackedAction<I>(configAction));
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public <S> void afterEach(Class<S> type, Closure<? super S> configAction) {
-            afterEach(type, new ClosureBackedAction<S>(configAction));
-        }
-
-        // TODO - mix this in
-        @Override
-        public Object getProperty(String property) {
-            I element = rawInstance.get(property);
-            if (element == null) {
-                throw new MissingPropertyException(property, CollectionBuilder.class);
-            }
-            return element;
-        }
-
-        // TODO - mix this in and validate closure parameters
-        public Void methodMissing(String name, Object argsObj) {
-            Object[] args = (Object[]) argsObj;
-            if (args.length == 1 && args[0] instanceof Class<?>) {
-                Class<? extends I> itemType = uncheckedCast(args[0]);
-                create(name, itemType);
-            } else if (args.length == 2 && args[0] instanceof Class<?> && args[1] instanceof Closure<?>) {
-                Class<? extends I> itemType = uncheckedCast(args[0]);
-                Closure<? super I> closure = uncheckedCast(args[1]);
-                create(name, itemType, closure);
-            } else if (args.length == 1 && args[0] instanceof Closure<?>) {
-                Closure<? super I> closure = uncheckedCast(args[0]);
-                named(name, closure);
-            } else {
-                throw new MissingMethodException(name, CollectionBuilder.class, args);
-            }
-            return null;
-        }
-
-        private void assertNotClosed() {
-            if (closed) {
-                throw new ModelViewClosedException(type, ruleDescriptor);
-            }
-        }
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultCollectionBuilder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultCollectionBuilder.java
deleted file mode 100644
index d9acfc6..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultCollectionBuilder.java
+++ /dev/null
@@ -1,314 +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.model.internal.core;
-
-import net.jcip.annotations.NotThreadSafe;
-import org.gradle.api.Action;
-import org.gradle.api.Nullable;
-import org.gradle.internal.Actions;
-import org.gradle.internal.BiAction;
-import org.gradle.internal.util.BiFunction;
-import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import static org.gradle.internal.Cast.uncheckedCast;
-
- at NotThreadSafe
-public class DefaultCollectionBuilder<T> implements CollectionBuilder<T> {
-
-    private final ModelType<T> elementType;
-    private final ModelRuleDescriptor sourceDescriptor;
-    private final MutableModelNode modelNode;
-    private final BiFunction<? extends ModelCreators.Builder, ? super ModelPath, ? super ModelType<? extends T>> creatorFunction;
-
-    public DefaultCollectionBuilder(ModelType<T> elementType, ModelRuleDescriptor sourceDescriptor, MutableModelNode modelNode, BiFunction<? extends ModelCreators.Builder, ? super ModelPath, ? super ModelType<? extends T>> creatorFunction) {
-        this.elementType = elementType;
-        this.sourceDescriptor = sourceDescriptor;
-        this.modelNode = modelNode;
-        this.creatorFunction = creatorFunction;
-    }
-
-    @Override
-    public String toString() {
-        return modelNode.getPrivateData().toString();
-    }
-
-    @Override
-    public int size() {
-        return modelNode.getLinkCount(elementType);
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-
-    @Override
-    public <S> CollectionBuilder<S> withType(Class<S> type) {
-        if (type.equals(elementType.getConcreteClass())) {
-            return uncheckedCast(this);
-        }
-
-        if (elementType.getConcreteClass().isAssignableFrom(type)) {
-            Class<? extends T> castType = uncheckedCast(type);
-            CollectionBuilder<? extends T> subType = toSubType(castType);
-            return uncheckedCast(subType);
-        }
-
-        return new DefaultCollectionBuilder<S>(ModelType.of(type), sourceDescriptor, modelNode, new BiFunction<ModelCreators.Builder, ModelPath, ModelType<? extends S>>() {
-            @Override
-            public ModelCreators.Builder apply(ModelPath s, ModelType<? extends S> modelType) {
-                throw new IllegalArgumentException(String.format("Cannot create an item of type %s as this is not a subtype of %s.", modelType, elementType.toString()));
-            }
-        });
-    }
-
-    public <S extends T> CollectionBuilder<S> toSubType(Class<S> type) {
-        return new DefaultCollectionBuilder<S>(ModelType.of(type), sourceDescriptor, modelNode, creatorFunction);
-    }
-
-    @Nullable
-    @Override
-    public T get(Object name) {
-        return get((String) name);
-    }
-
-    @Nullable
-    @Override
-    public T get(String name) {
-        // TODO - lock this down
-        MutableModelNode link = modelNode.getLink(name);
-        if (link == null) {
-            return null;
-        }
-        link.ensureUsable();
-        return link.asWritable(elementType, sourceDescriptor, null).getInstance();
-    }
-
-    @Override
-    public boolean containsKey(Object name) {
-        return name instanceof String && modelNode.hasLink((String) name, elementType);
-    }
-
-    @Override
-    public Set<String> keySet() {
-        return modelNode.getLinkNames(elementType);
-    }
-
-    @Override
-    public boolean containsValue(Object item) {
-        throw new UnsupportedOperationException("Not implemented yet.");
-    }
-
-    @Override
-    public void create(final String name) {
-        doCreate(name, elementType);
-    }
-
-    @Override
-    public void create(String name, Action<? super T> configAction) {
-        doCreate(name, elementType, configAction);
-    }
-
-    @Override
-    public <S extends T> void create(final String name, final Class<S> type) {
-        doCreate(name, ModelType.of(type));
-    }
-
-    @Override
-    public <S extends T> void create(final String name, final Class<S> type, final Action<? super S> configAction) {
-        doCreate(name, ModelType.of(type), configAction);
-    }
-
-    private <S extends T> void doCreate(final String name, final ModelType<S> type) {
-        doCreate(name, type, Actions.doNothing());
-    }
-
-    private <S extends T> void doCreate(final String name, final ModelType<S> type, final Action<? super S> initAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "create(%s)", name);
-
-        ModelCreators.Builder creatorBuilder = creatorFunction.apply(modelNode.getPath().child(name), type);
-
-        ModelCreator creator = creatorBuilder
-                .withProjection(new UnmanagedModelProjection<S>(type, true, true))
-                .descriptor(descriptor)
-                .build();
-
-        modelNode.addLink(creator);
-
-        modelNode.applyToLink(ModelActionRole.Initialize, new ActionBackedModelAction<S>(ModelReference.of(creator.getPath(), type), descriptor, new Action<S>() {
-            @Override
-            public void execute(S s) {
-                initAction.execute(s);
-            }
-        }));
-
-        onCreate(name, type);
-    }
-
-    protected <S extends T> void onCreate(String name, ModelType<S> type) {
-
-    }
-
-    @Override
-    public void named(final String name, Action<? super T> configAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "named(%s)", name);
-        ModelReference<T> subject = ModelReference.of(modelNode.getPath().child(name), elementType);
-        modelNode.applyToLink(ModelActionRole.Mutate, new ActionBackedModelAction<T>(subject, descriptor, configAction));
-    }
-
-    @Override
-    public void named(String name, Class<? extends RuleSource> ruleSource) {
-        modelNode.applyToLink(name, ruleSource);
-    }
-
-    @Override
-    public void all(final Action<? super T> configAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "all()");
-        ModelReference<T> subject = ModelReference.of(elementType);
-        modelNode.applyToAllLinks(ModelActionRole.Mutate, new ActionBackedModelAction<T>(subject, descriptor, configAction));
-    }
-
-    @Override
-    public <S> void withType(Class<S> type, Action<? super S> configAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "withType()");
-        ModelReference<S> subject = ModelReference.of(type);
-        modelNode.applyToAllLinks(ModelActionRole.Mutate, new ActionBackedModelAction<S>(subject, descriptor, configAction));
-    }
-
-    @Override
-    public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
-        modelNode.applyToLinks(type, rules);
-    }
-
-    @Override
-    public void beforeEach(Action<? super T> configAction) {
-        doBeforeEach(elementType, configAction);
-    }
-
-    @Override
-    public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
-        doBeforeEach(ModelType.of(type), configAction);
-    }
-
-    private <S> void doBeforeEach(ModelType<S> type, Action<? super S> configAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "beforeEach()");
-        ModelReference<S> subject = ModelReference.of(type);
-        modelNode.applyToAllLinks(ModelActionRole.Defaults, new ActionBackedModelAction<S>(subject, descriptor, configAction));
-    }
-
-    @Override
-    public void afterEach(Action<? super T> configAction) {
-        doFinalizeAll(elementType, configAction);
-    }
-
-    @Override
-    public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
-        doFinalizeAll(ModelType.of(type), configAction);
-    }
-
-    private <S> void doFinalizeAll(ModelType<S> type, Action<? super S> configAction) {
-        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "afterEach()");
-        ModelReference<S> subject = ModelReference.of(type);
-        modelNode.applyToAllLinks(ModelActionRole.Finalize, new ActionBackedModelAction<S>(subject, descriptor, configAction));
-    }
-
-    public static <I> ModelType<CollectionBuilder<I>> typeOf(ModelType<I> type) {
-        return new ModelType.Builder<CollectionBuilder<I>>() {
-        }.where(
-                new ModelType.Parameter<I>() {
-                }, type
-        ).build();
-    }
-
-    public static <I> ModelType<CollectionBuilder<I>> typeOf(Class<I> type) {
-        return typeOf(ModelType.of(type));
-    }
-
-    public static <I> ModelType<NamedEntityInstantiator<I>> instantiatorTypeOf(Class<I> type) {
-        return instantiatorTypeOf(ModelType.of(type));
-    }
-
-    public static <I> ModelType<NamedEntityInstantiator<I>> instantiatorTypeOf(ModelType<I> type) {
-        return new ModelType.Builder<NamedEntityInstantiator<I>>() {
-        }.where(
-                new ModelType.Parameter<I>() {
-                }, type
-        ).build();
-    }
-
-    public static <T> BiFunction<ModelCreators.Builder, ModelPath, ModelType<? extends T>> createAndStoreVia(final ModelReference<? extends NamedEntityInstantiator<? super T>> instantiatorReference, final ModelReference<? extends Collection<? super T>> storeReference) {
-        return new BiFunction<ModelCreators.Builder, ModelPath, ModelType<? extends T>>() {
-            @Override
-            public ModelCreators.Builder apply(final ModelPath path, final ModelType<? extends T> modelType) {
-                return ModelCreators
-                        .of(ModelReference.of(path, modelType), new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                            @Override
-                            public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
-                                doExecute(modelNode, modelType, modelViews);
-                            }
-
-                            public <S extends T> void doExecute(MutableModelNode modelNode, ModelType<S> subType, List<ModelView<?>> modelViews) {
-                                NamedEntityInstantiator<? super T> instantiator = ModelViews.getInstance(modelViews.get(0), instantiatorReference);
-                                S item = instantiator.create(path.getName(), subType.getConcreteClass());
-                                modelNode.setPrivateData(subType, item);
-                                modelNode.applyToSelf(ModelActionRole.Initialize, BiActionBackedModelAction.single(ModelReference.of(path, subType), new SimpleModelRuleDescriptor("DefaultCollectionBuilder.createAndStoreVia() - " + path), storeReference, new BiAction<S, Collection<? super T>>() {
-                                    @Override
-                                    public void execute(S s, Collection<? super T> objects) {
-                                        objects.add(s);
-                                    }
-                                }));
-                            }
-                        })
-                        .inputs(instantiatorReference);
-
-            }
-        };
-    }
-
-    public static <T> BiFunction<ModelCreators.Builder, ModelPath, ModelType<? extends T>> createVia(final ModelReference<? extends NamedEntityInstantiator<? super T>> instantiatorReference) {
-        return new BiFunction<ModelCreators.Builder, ModelPath, ModelType<? extends T>>() {
-            @Override
-            public ModelCreators.Builder apply(final ModelPath path, final ModelType<? extends T> modelType) {
-                return ModelCreators
-                        .of(ModelReference.of(path, modelType), new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                            @Override
-                            public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
-                                doExecute(modelNode, modelType, modelViews);
-                            }
-
-                            public <S extends T> void doExecute(MutableModelNode modelNode, ModelType<S> subType, List<ModelView<?>> modelViews) {
-                                NamedEntityInstantiator<? super T> instantiator = ModelViews.getInstance(modelViews.get(0), instantiatorReference);
-                                S item = instantiator.create(path.getName(), subType.getConcreteClass());
-                                modelNode.setPrivateData(subType, item);
-                            }
-                        })
-                        .inputs(instantiatorReference);
-
-            }
-        };
-    }
-
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java
new file mode 100644
index 0000000..6a48f50
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import net.jcip.annotations.NotThreadSafe;
+import org.gradle.api.Action;
+import org.gradle.model.ModelViewClosedException;
+import org.gradle.model.WriteOnlyModelViewException;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+
+ at NotThreadSafe
+public class DefaultModelViewState implements ModelViewState {
+
+    private final ModelType<?> type;
+    private final ModelRuleDescriptor ruleDescriptor;
+    private boolean closed;
+    private final boolean mutable;
+    private final boolean canReadChildren;
+
+    public DefaultModelViewState(ModelType<?> type, ModelRuleDescriptor ruleDescriptor, boolean mutable, boolean canReadChildren) {
+        this.type = type;
+        this.ruleDescriptor = ruleDescriptor;
+        this.mutable = mutable;
+        this.canReadChildren = canReadChildren;
+    }
+
+    public void close() {
+        closed = true;
+    }
+
+    public Action<Object> closer() {
+        return new Action<Object>() {
+            @Override
+            public void execute(Object o) {
+                close();
+            }
+        };
+    }
+
+    @Override
+    public void assertCanMutate() {
+        if (!mutable || closed) {
+            throw new ModelViewClosedException(type, ruleDescriptor);
+        }
+    }
+
+    @Override
+    public void assertCanReadChildren() {
+        if (!canReadChildren) {
+            throw new WriteOnlyModelViewException(type, ruleDescriptor);
+        }
+    }
+
+    @Override
+    public boolean isCanMutate() {
+        return mutable && !closed;
+    }
+
+    @Override
+    public boolean isCanReadChildren() {
+        return canReadChildren;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DelegatingCollectionBuilder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DelegatingCollectionBuilder.java
deleted file mode 100644
index 906313e..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DelegatingCollectionBuilder.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.model.internal.core;
-
-import org.gradle.api.Action;
-import org.gradle.api.Nullable;
-import org.gradle.internal.BiAction;
-import org.gradle.internal.Cast;
-import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Set;
-
-public class DelegatingCollectionBuilder<T> implements CollectionBuilder<T> {
-
-    private final CollectionBuilder<T> delegate;
-    private final ModelType<T> baseType;
-    private final BiAction<? super String, ? super ModelType<? extends T>> onCreate;
-
-    public DelegatingCollectionBuilder(CollectionBuilder<T> delegate, ModelType<T> baseType, BiAction<? super String, ? super ModelType<? extends T>> onCreate) {
-        this.delegate = delegate;
-        this.baseType = baseType;
-        this.onCreate = onCreate;
-    }
-
-    @Override
-    public <S> CollectionBuilder<S> withType(Class<S> type) {
-        // This cast is safe, because we know that .create() will fail on the real collection if S doesn't extend T
-        BiAction<? super String, ? super ModelType<? extends S>> castOnCreate = Cast.uncheckedCast(onCreate);
-        return new DelegatingCollectionBuilder<S>(delegate.withType(type), ModelType.of(type), castOnCreate);
-    }
-
-    @Override
-    public int size() {
-        return delegate.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return delegate.isEmpty();
-    }
-
-    @Override
-    @Nullable
-    public T get(Object name) {
-        return delegate.get(name);
-    }
-
-    @Override
-    @Nullable
-    public T get(String name) {
-        return delegate.get(name);
-    }
-
-    @Override
-    public boolean containsKey(Object name) {
-        return delegate.containsKey(name);
-    }
-
-    @Override
-    public boolean containsValue(Object item) {
-        return delegate.containsValue(item);
-    }
-
-    @Override
-    public Set<String> keySet() {
-        return delegate.keySet();
-    }
-
-    @Override
-    public void create(String name) {
-        delegate.create(name);
-        onCreate(name, baseType);
-    }
-
-    @Override
-    public void create(String name, Action<? super T> configAction) {
-        delegate.create(name, configAction);
-        onCreate(name, baseType);
-    }
-
-    @Override
-    public <S extends T> void create(String name, Class<S> type) {
-        delegate.create(name, type);
-        onCreate(name, ModelType.of(type));
-    }
-
-    @Override
-    public <S extends T> void create(String name, Class<S> type, Action<? super S> configAction) {
-        onCreate(name, ModelType.of(type));
-        delegate.create(name, type, configAction);
-    }
-
-    @Override
-    public void named(String name, Action<? super T> configAction) {
-        delegate.named(name, configAction);
-    }
-
-    @Override
-    public void named(String name, Class<? extends RuleSource> ruleSource) {
-        delegate.named(name, ruleSource);
-    }
-
-    @Override
-    public void beforeEach(Action<? super T> configAction) {
-        delegate.beforeEach(configAction);
-    }
-
-    @Override
-    public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
-        delegate.beforeEach(type, configAction);
-    }
-
-    @Override
-    public void all(Action<? super T> configAction) {
-        delegate.all(configAction);
-    }
-
-    @Override
-    public <S> void withType(Class<S> type, Action<? super S> configAction) {
-        delegate.withType(type, configAction);
-    }
-
-    @Override
-    public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
-        delegate.withType(type, rules);
-    }
-
-    @Override
-    public void afterEach(Action<? super T> configAction) {
-        delegate.afterEach(configAction);
-    }
-
-    @Override
-    public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
-        delegate.afterEach(type, configAction);
-    }
-
-    private <S extends T> void onCreate(String name, ModelType<S> type) {
-        onCreate.execute(name, type);
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeInputUsingModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeInputUsingModelAction.java
new file mode 100644
index 0000000..77e8f6e
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeInputUsingModelAction.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import org.gradle.internal.TriAction;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.List;
+
+public class DirectNodeInputUsingModelAction<T> implements ModelAction<T> {
+    private final ModelReference<T> subject;
+    private final ModelRuleDescriptor descriptor;
+    private final List<ModelReference<?>> inputs;
+    private final TriAction<? super MutableModelNode, ? super T, ? super List<ModelView<?>>> action;
+
+    public DirectNodeInputUsingModelAction(ModelReference<T> subject, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs,
+                                           TriAction<? super MutableModelNode, ? super T, ? super List<ModelView<?>>> action) {
+        this.subject = subject;
+        this.descriptor = descriptor;
+        this.inputs = inputs;
+        this.action = action;
+    }
+
+    public static <T> DirectNodeInputUsingModelAction<T> of(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs,
+                                                      TriAction<? super MutableModelNode, ? super T, ? super List<ModelView<?>>> action) {
+        return new DirectNodeInputUsingModelAction<T>(modelReference, descriptor, inputs, action);
+    }
+
+    @Override
+    public ModelReference<T> getSubject() {
+        return subject;
+    }
+
+    @Override
+    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
+        action.execute(modelNode, object, inputs);
+    }
+
+    @Override
+    public List<ModelReference<?>> getInputs() {
+        return inputs;
+    }
+
+    @Override
+    public ModelRuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeModelAction.java
deleted file mode 100644
index d590cde..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeModelAction.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.model.internal.core;
-
-import org.gradle.api.Action;
-import org.gradle.internal.BiAction;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-
-import java.util.Collections;
-import java.util.List;
-
-public class DirectNodeModelAction<T> implements ModelAction<T> {
-
-    private final ModelReference<T> subjectReference;
-    private final BiAction<? super MutableModelNode, ? super T> action;
-    private final ModelRuleDescriptor descriptor;
-
-    private DirectNodeModelAction(ModelReference<T> subjectReference, ModelRuleDescriptor descriptor, BiAction<? super MutableModelNode, ? super T> action) {
-        this.subjectReference = subjectReference;
-        this.action = action;
-        this.descriptor = descriptor;
-    }
-
-    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, final Action<? super MutableModelNode> action) {
-        return new DirectNodeModelAction<T>(reference, descriptor, new BiAction<MutableModelNode, T>() {
-            @Override
-            public void execute(MutableModelNode modelNode, T t) {
-                action.execute(modelNode);
-            }
-        });
-    }
-
-    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, BiAction<? super MutableModelNode, ? super T> action) {
-        return new DirectNodeModelAction<T>(reference, descriptor, action);
-    }
-
-    @Override
-    public ModelReference<T> getSubject() {
-        return subjectReference;
-    }
-
-    @Override
-    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
-        action.execute(modelNode, object);
-    }
-
-    @Override
-    public List<ModelReference<?>> getInputs() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public ModelRuleDescriptor getDescriptor() {
-        return descriptor;
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeNoInputsModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeNoInputsModelAction.java
new file mode 100644
index 0000000..d720ed0
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DirectNodeNoInputsModelAction.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.model.internal.core;
+
+import org.gradle.api.Action;
+import org.gradle.internal.BiAction;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Collections;
+import java.util.List;
+
+public class DirectNodeNoInputsModelAction<T> implements ModelAction<T> {
+
+    private final ModelReference<T> subjectReference;
+    private final BiAction<? super MutableModelNode, ? super T> action;
+    private final ModelRuleDescriptor descriptor;
+
+    private DirectNodeNoInputsModelAction(ModelReference<T> subjectReference, ModelRuleDescriptor descriptor, BiAction<? super MutableModelNode, ? super T> action) {
+        this.subjectReference = subjectReference;
+        this.action = action;
+        this.descriptor = descriptor;
+    }
+
+    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, final Action<? super MutableModelNode> action) {
+        return new DirectNodeNoInputsModelAction<T>(reference, descriptor, new BiAction<MutableModelNode, T>() {
+            @Override
+            public void execute(MutableModelNode modelNode, T t) {
+                action.execute(modelNode);
+            }
+        });
+    }
+
+    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, BiAction<? super MutableModelNode, ? super T> action) {
+        return new DirectNodeNoInputsModelAction<T>(reference, descriptor, action);
+    }
+
+    @Override
+    public ModelReference<T> getSubject() {
+        return subjectReference;
+    }
+
+    @Override
+    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
+        action.execute(modelNode, object);
+    }
+
+    @Override
+    public List<ModelReference<?>> getInputs() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public ModelRuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/EmptyModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/EmptyModelProjection.java
index ed7b7a5..c0bdce5 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/EmptyModelProjection.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/EmptyModelProjection.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Optional;
 import org.gradle.api.Nullable;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.type.ModelType;
@@ -53,12 +54,17 @@ public class EmptyModelProjection implements ModelProjection {
     }
 
     @Override
-    public Iterable<String> getWritableTypeDescriptions() {
+    public Iterable<String> getWritableTypeDescriptions(MutableModelNode node) {
         return Collections.emptyList();
     }
 
     @Override
-    public Iterable<String> getReadableTypeDescriptions() {
+    public Iterable<String> getReadableTypeDescriptions(MutableModelNode node) {
         return Collections.emptyList();
     }
+
+    @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InputUsingModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InputUsingModelAction.java
new file mode 100644
index 0000000..bfcc907
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InputUsingModelAction.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.model.internal.core;
+
+import org.gradle.internal.BiAction;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Collections;
+import java.util.List;
+
+public class InputUsingModelAction<T> implements ModelAction<T> {
+    private final ModelReference<T> modelReference;
+    private final ModelRuleDescriptor descriptor;
+    private final List<ModelReference<?>> inputs;
+    private final BiAction<? super T, ? super List<ModelView<?>>> initializer;
+
+    public InputUsingModelAction(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs, BiAction<? super T, ? super List<ModelView<?>>> initializer) {
+        this.modelReference = modelReference;
+        this.descriptor = descriptor;
+        this.inputs = inputs;
+        this.initializer = initializer;
+    }
+
+    public static <T> InputUsingModelAction<T> of(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, List<ModelReference<?>> inputs, BiAction<? super T, ? super List<ModelView<?>>> initializer) {
+        return new InputUsingModelAction<T>(modelReference, descriptor, inputs, initializer);
+    }
+
+    public static <T, I> InputUsingModelAction<T> single(ModelReference<T> modelReference, ModelRuleDescriptor descriptor, final ModelReference<I> input, final BiAction<? super T, ? super I> initializer) {
+        return new InputUsingModelAction<T>(modelReference, descriptor, Collections.<ModelReference<?>>singletonList(input), new BiAction<T, List<ModelView<?>>>() {
+            @Override
+            public void execute(T t, List<ModelView<?>> modelViews) {
+                initializer.execute(t, ModelViews.assertType(modelViews.get(0), input.getType()).getInstance());
+            }
+        });
+    }
+
+    @Override
+    public ModelReference<T> getSubject() {
+        return modelReference;
+    }
+
+    @Override
+    public ModelRuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+
+    @Override
+    public List<ModelReference<?>> getInputs() {
+        return inputs;
+    }
+
+    @Override
+    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
+        initializer.execute(object, inputs);
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceFactory.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceFactory.java
new file mode 100644
index 0000000..da86434
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.util.BiFunction;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+public interface InstanceFactory<T, P> {
+    <S extends T> S create(Class<S> type, MutableModelNode modelNode, P payload);
+
+    String getSupportedTypeNames();
+
+    <S extends T> void register(Class<S> type, @Nullable ModelRuleDescriptor sourceRule, BiFunction<? extends S, ? super P, ? super MutableModelNode> factory);
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java
index 73f199c..f05f5e3 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java
@@ -17,6 +17,8 @@
 package org.gradle.model.internal.core;
 
 import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.Action;
+import org.gradle.internal.Actions;
 import org.gradle.model.internal.type.ModelType;
 
 @ThreadSafe
@@ -25,15 +27,21 @@ public class InstanceModelView<T> implements ModelView<T> {
     private final ModelPath path;
     private final ModelType<T> type;
     private final T instance;
+    private final Action<? super T> onClose;
 
-    public InstanceModelView(ModelPath path, ModelType<T> type, T instance) {
+    public InstanceModelView(ModelPath path, ModelType<T> type, T instance, Action<? super T> onClose) {
         this.path = path;
         this.type = type;
         this.instance = instance;
+        this.onClose = onClose;
     }
 
     public static <T> ModelView<T> of(ModelPath path, ModelType<T> type, T instance) {
-        return new InstanceModelView<T>(path, type, instance);
+        return of(path, type, instance, Actions.doNothing());
+    }
+
+    public static <T> ModelView<T> of(ModelPath path, ModelType<T> type, T instance, Action<? super T> onClose) {
+        return new InstanceModelView<T>(path, type, instance, onClose);
     }
 
     @Override
@@ -50,6 +58,6 @@ public class InstanceModelView<T> implements ModelView<T> {
     }
 
     public void close() {
-
+        onClose.execute(instance);
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelActionRole.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelActionRole.java
index 4c5984f..c6e76ee 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelActionRole.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelActionRole.java
@@ -16,15 +16,29 @@
 
 package org.gradle.model.internal.core;
 
+import org.gradle.api.Nullable;
+
 /**
  * A hard-coded sequence of model actions that can be applied to a model element.
  *
  * <p>This is pretty much a placeholder for something more descriptive.
  */
 public enum ModelActionRole {
-    Defaults, // Allows a mutation to setup default values for an element
-    Initialize, // Mutation provided when an element is defined
-    Mutate, // Customisations
-    Finalize, // Post customisation default values
-    Validate // Post mutation validations
+    DefineRules(null), // Defines rules for an element. Does not use the subject as input and may run at any time after the element is known
+    Defaults(ModelNode.State.DefaultsApplied), // Allows a mutation to setup default values for an element
+    Initialize(ModelNode.State.Initialized), // Mutation action provided when an element is defined
+    Mutate(ModelNode.State.Mutated), // Customisations
+    Finalize(ModelNode.State.Finalized), // Post customisation default values
+    Validate(ModelNode.State.SelfClosed); // Post mutation validations
+
+    private final ModelNode.State target;
+
+    ModelActionRole(ModelNode.State target) {
+        this.target = target;
+    }
+
+    @Nullable
+    public ModelNode.State getTargetState() {
+        return target;
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelAdapter.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelAdapter.java
index 9568207..a69756b 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelAdapter.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelAdapter.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Optional;
 import org.gradle.api.Nullable;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.type.ModelType;
@@ -33,4 +34,6 @@ public interface ModelAdapter {
     @Override
         // must implement logical equality
     boolean equals(Object other);
+
+    Optional<String> getValueDescription(MutableModelNode modelNodeInternal);
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreator.java
index 20187cb..cd4432c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreator.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreator.java
@@ -33,5 +33,5 @@ public interface ModelCreator extends ModelRule {
 
     boolean isEphemeral();
 
-    List<? extends ModelReference<?>> getInputs();
+    List<ModelReference<?>> getInputs();
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreators.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreators.java
index 69d859e..962bc6d 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreators.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelCreators.java
@@ -16,12 +16,14 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.collect.Lists;
 import net.jcip.annotations.NotThreadSafe;
 import net.jcip.annotations.ThreadSafe;
-import org.gradle.internal.BiAction;
-import org.gradle.internal.Factories;
-import org.gradle.internal.Factory;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.internal.*;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
 import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
 
 import java.util.ArrayList;
@@ -33,37 +35,71 @@ import java.util.List;
 abstract public class ModelCreators {
 
     public static <T> Builder bridgedInstance(ModelReference<T> modelReference, T instance) {
-        return unmanagedInstance(modelReference, Factories.constant(instance));
+        return unmanagedInstance(modelReference, Factories.constant(instance), Actions.doNothing());
     }
 
     public static <T> Builder unmanagedInstance(final ModelReference<T> modelReference, final Factory<? extends T> factory) {
-        BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer = new BiAction<MutableModelNode, List<ModelView<?>>>() {
-            public void execute(MutableModelNode modelNode, List<ModelView<?>> inputs) {
-                modelNode.setPrivateData(modelReference.getType(), factory.create());
+        return unmanagedInstance(modelReference, factory, Actions.doNothing());
+    }
+
+    public static <T> Builder unmanagedInstance(final ModelReference<T> modelReference, final Factory<? extends T> factory, final Action<? super MutableModelNode> initializer) {
+        return unmanagedInstanceOf(modelReference, new Transformer<T, MutableModelNode>() {
+            @Override
+            public T transform(MutableModelNode modelNode) {
+                T t = factory.create();
+                initializer.execute(modelNode);
+                return t;
+            }
+        });
+    }
+
+    public static <T> Builder unmanagedInstanceOf(final ModelReference<T> modelReference, final Transformer<? extends T, ? super MutableModelNode> factory) {
+        return of(modelReference.getPath(), new Action<MutableModelNode>() {
+            @Override
+            public void execute(MutableModelNode modelNode) {
+                T t = factory.transform(modelNode);
+                modelNode.setPrivateData(modelReference.getType(), t);
             }
-        };
+        })
+            .withProjection(UnmanagedModelProjection.of(modelReference.getType()));
+    }
 
-        return of(modelReference, initializer)
-                .withProjection(new UnmanagedModelProjection<T>(modelReference.getType(), true, true));
+    public static Builder of(ModelPath path) {
+        return new Builder(path, BiActions.doNothing());
     }
 
-    public static Builder of(ModelReference<?> modelReference, BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer) {
-        return new Builder(modelReference, initializer);
+    public static Builder of(ModelPath path, BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer) {
+        return new Builder(path, initializer);
+    }
+
+    public static Builder of(ModelPath path, Action<? super MutableModelNode> initializer) {
+        return new Builder(path, BiActions.usingFirstArgument(initializer));
+    }
+
+    public static <T> Builder of(final ModelReference<T> modelReference, final Factory<? extends T> factory) {
+        return of(modelReference.getPath(), new Action<MutableModelNode>() {
+            @Override
+            public void execute(MutableModelNode modelNode) {
+                T value = factory.create();
+                modelNode.setPrivateData(modelReference.getType(), value);
+            }
+        });
     }
 
     @NotThreadSafe
     public static class Builder {
         private final BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer;
-        private final ModelReference<?> modelReference;
+        private final ModelPath path;
         private final List<ModelProjection> projections = new ArrayList<ModelProjection>();
+        private final List<Pair<? extends ModelActionRole, ? extends ModelAction<?>>> actions = Lists.newArrayList();
         private boolean ephemeral;
         private boolean hidden;
 
         private ModelRuleDescriptor modelRuleDescriptor;
-        private List<? extends ModelReference<?>> inputs = Collections.emptyList();
+        private List<ModelReference<?>> inputs = Collections.emptyList();
 
-        private Builder(ModelReference<?> modelReference, BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer) {
-            this.modelReference = modelReference;
+        private Builder(ModelPath path, BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer) {
+            this.path = path;
             this.initializer = initializer;
         }
 
@@ -77,7 +113,22 @@ abstract public class ModelCreators {
             return this;
         }
 
-        public Builder inputs(List<? extends ModelReference<?>> inputs) {
+        public Builder descriptor(ModelRuleDescriptor parent, ModelRuleDescriptor child) {
+            this.modelRuleDescriptor = new NestedModelRuleDescriptor(parent, child);
+            return this;
+        }
+
+        public Builder descriptor(ModelRuleDescriptor parent, String child) {
+            this.modelRuleDescriptor = new NestedModelRuleDescriptor(parent, new SimpleModelRuleDescriptor(child));
+            return this;
+        }
+
+        public Builder action(ModelActionRole role, ModelAction<?> action) {
+            this.actions.add(Pair.of(role, action));
+            return this;
+        }
+
+        public Builder inputs(List<ModelReference<?>> inputs) {
             this.inputs = inputs;
             return this;
         }
@@ -103,9 +154,22 @@ abstract public class ModelCreators {
             return this;
         }
 
+        @SuppressWarnings("unchecked")
         public ModelCreator build() {
             ModelProjection projection = projections.size() == 1 ? projections.get(0) : new ChainingModelProjection(projections);
-            return new ProjectionBackedModelCreator(modelReference.getPath(), modelRuleDescriptor, ephemeral, hidden, inputs, projection, initializer);
+
+            BiAction<? super MutableModelNode, ? super List<ModelView<?>>> effectiveInitializer = initializer;
+            if (!actions.isEmpty()) {
+                effectiveInitializer = BiActions.composite(initializer, new BiAction<MutableModelNode, List<ModelView<?>>>() {
+                    @Override
+                    public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
+                        for (Pair<? extends ModelActionRole, ? extends ModelAction<?>> action : actions) {
+                            modelNode.applyToSelf(action.getLeft(), action.getRight());
+                        }
+                    }
+                });
+            }
+            return new ProjectionBackedModelCreator(path, modelRuleDescriptor, ephemeral, hidden, inputs, projection, effectiveInitializer);
         }
     }
 
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelMapGroovyDecorator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelMapGroovyDecorator.java
new file mode 100644
index 0000000..9413611
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelMapGroovyDecorator.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.Action;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.model.ModelMap;
+import org.gradle.model.RuleSource;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+/**
+ * Used as the superclass for views for types that extend {@link org.gradle.model.ModelMap}.
+ */
+// TODO - mix in Groovy support
+public class ModelMapGroovyDecorator<I> extends GroovyObjectSupport implements ModelMap<I> {
+
+    private final ModelMap<I> delegate;
+
+    public static <T> ModelMap<T> wrap(ModelMap<T> delegate) {
+        return new ModelMapGroovyDecorator<T>(delegate);
+    }
+
+    public ModelMapGroovyDecorator(ModelMap<I> delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public <S> ModelMap<S> withType(Class<S> type) {
+        return new ModelMapGroovyDecorator<S>(delegate.withType(type));
+    }
+
+    @Override
+    public int size() {
+        return delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    @Override
+    @Nullable
+    public I get(Object name) {
+        return delegate.get(name);
+    }
+
+    @Override
+    @Nullable
+    public I get(String name) {
+        return delegate.get(name);
+    }
+
+    @Override
+    public boolean containsKey(Object name) {
+        return delegate.containsKey(name);
+    }
+
+    @Override
+    public boolean containsValue(Object item) {
+        return delegate.containsValue(item);
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return delegate.keySet();
+    }
+
+    @Override
+    public void create(String name) {
+        delegate.create(name);
+    }
+
+    @Override
+    public void create(String name, Action<? super I> configAction) {
+        delegate.create(name, configAction);
+    }
+
+    @Override
+    public <S extends I> void create(String name, Class<S> type) {
+        delegate.create(name, type);
+    }
+
+    @Override
+    public <S extends I> void create(String name, Class<S> type, Action<? super S> configAction) {
+        delegate.create(name, type, configAction);
+    }
+
+    @Override
+    public void named(String name, Action<? super I> configAction) {
+        delegate.named(name, configAction);
+    }
+
+    @Override
+    public void named(String name, Class<? extends RuleSource> ruleSource) {
+        delegate.named(name, ruleSource);
+    }
+
+    @Override
+    public void beforeEach(Action<? super I> configAction) {
+        delegate.beforeEach(configAction);
+    }
+
+    @Override
+    public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
+        delegate.beforeEach(type, configAction);
+    }
+
+    @Override
+    public void all(Action<? super I> configAction) {
+        delegate.all(configAction);
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Action<? super S> configAction) {
+        delegate.withType(type, configAction);
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
+        delegate.withType(type, rules);
+    }
+
+    @Override
+    public void afterEach(Action<? super I> configAction) {
+        delegate.afterEach(configAction);
+    }
+
+    @Override
+    public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
+        delegate.afterEach(type, configAction);
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+
+    @Override
+    public Collection<I> values() {
+        return delegate.values();
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public void create(String name, Closure<? super I> configAction) {
+        create(name, new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public <S extends I> void create(String name, Class<S> type, Closure<? super S> configAction) {
+        create(name, type, new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public void named(String name, Closure<? super I> configAction) {
+        named(name, new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public void all(Closure<? super I> configAction) {
+        all(new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public <S> void withType(Class<S> type, Closure<? super S> configAction) {
+        withType(type, new ClosureBackedAction<S>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public void beforeEach(Closure<? super I> configAction) {
+        beforeEach(new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public <S> void beforeEach(Class<S> type, Closure<? super S> configAction) {
+        beforeEach(type, new ClosureBackedAction<S>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public void afterEach(Closure<? super I> configAction) {
+        afterEach(new ClosureBackedAction<I>(configAction));
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public <S> void afterEach(Class<S> type, Closure<? super S> configAction) {
+        afterEach(type, new ClosureBackedAction<S>(configAction));
+    }
+
+    // TODO - mix this in
+    @Override
+    public Object getProperty(String property) {
+        I element = delegate.get(property);
+        if (element == null) {
+            throw new MissingPropertyException(property, ModelMap.class);
+        }
+        return element;
+    }
+
+    // TODO - mix this in and validate closure parameters
+    public Void methodMissing(String name, Object argsObj) {
+        Object[] args = (Object[]) argsObj;
+        if (args.length == 1 && args[0] instanceof Class<?>) {
+            Class<? extends I> itemType = uncheckedCast(args[0]);
+            create(name, itemType);
+        } else if (args.length == 2 && args[0] instanceof Class<?> && args[1] instanceof Closure<?>) {
+            Class<? extends I> itemType = uncheckedCast(args[0]);
+            Closure<? super I> closure = uncheckedCast(args[1]);
+            create(name, itemType, closure);
+        } else if (args.length == 1 && args[0] instanceof Closure<?>) {
+            Closure<? super I> closure = uncheckedCast(args[0]);
+            named(name, closure);
+        } else {
+            throw new MissingMethodException(name, ModelMap.class, args);
+        }
+        return null;
+    }
+
+}
+
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelNode.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelNode.java
index f187c1f..4c9581a 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelNode.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelNode.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Optional;
 import org.gradle.api.Nullable;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.type.ModelType;
@@ -28,11 +29,11 @@ public interface ModelNode {
 
     boolean hasLink(String name, ModelType<?> type);
 
-    // Note: order is crucial here
+    // Note: order is crucial here. Nodes are traversed through these states in the order defined below
     public enum State {
-        Known(true),
-        Created(true),
-        DefaultsApplied(true),
+        Known(true), // Initial state. Only type info is available here
+        Created(true), // Private data has been created, initial rules discovered
+        DefaultsApplied(true), // Default values have been applied
         Initialized(true),
         Mutated(true),
         Finalized(false),
@@ -44,6 +45,10 @@ public interface ModelNode {
         State(boolean mutable) {
             this.mutable = mutable;
         }
+
+        public State previous() {
+            return ModelNode.State.values()[ordinal() - 1];
+        }
     }
 
     boolean isEphemeral();
@@ -71,4 +76,16 @@ public interface ModelNode {
      * Should this node be hidden from the model report.
      */
     boolean isHidden();
+
+    /**
+     * The number of link this node has.
+     */
+    int getLinkCount();
+
+    /**
+     * Gets the value represented by this node.
+     *
+     * Calling this method may create or transition the node.
+     */
+    Optional<String> getValueDescription();
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java
index 3360a8e..726ce63 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java
@@ -82,7 +82,7 @@ public class ModelPath implements Iterable<String>, Comparable<ModelPath> {
 
         ModelPath modelPath = (ModelPath) o;
 
-        return path.equals(modelPath.path);
+        return components.size() == modelPath.components.size() && path.equals(modelPath.path);
     }
 
     @Override
@@ -125,23 +125,11 @@ public class ModelPath implements Iterable<String>, Comparable<ModelPath> {
         return path(childComponents);
     }
 
-    public ModelPath sibling(String name) {
-        if (this == ROOT) {
-            throw new IllegalStateException("Cannot create sibling path of root path");
-        }
-        List<String> newComponents = new ArrayList<String>(components);
-        newComponents.set(newComponents.size() - 1, name);
-        return path(newComponents);
-    }
-
-    public boolean isTopLevel() {
-        return getDepth() == 1;
-    }
-
     public ModelPath getRootParent() {
         return components.size() <= 1 ? null : ModelPath.path(components.get(0));
     }
 
+    @Nullable
     public ModelPath getParent() {
         if (components.isEmpty()) {
             return null;
@@ -149,7 +137,10 @@ public class ModelPath implements Iterable<String>, Comparable<ModelPath> {
         if (components.size() == 1) {
             return ROOT;
         }
-        return path(components.subList(0, components.size() - 1));
+        // Somewhat optimized implementation
+        List<String> parentComponents = components.subList(0, components.size() - 1);
+        String parentPath = path.substring(0, path.length() - components.get(components.size() - 1).length() - 1);
+        return new ModelPath(parentPath, parentComponents);
     }
 
     public String getName() {
@@ -170,6 +161,16 @@ public class ModelPath implements Iterable<String>, Comparable<ModelPath> {
         return otherParent != null && otherParent.equals(this);
     }
 
+    public boolean isDescendant(@Nullable ModelPath other) {
+        if (other == null) {
+            return false;
+        }
+        if (other.getDepth() <= getDepth()) {
+            return false;
+        }
+        return getComponents().equals(other.getComponents().subList(0, getDepth()));
+    }
+
     public ModelPath descendant(ModelPath path) {
         return path(Iterables.concat(components, path.components));
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPredicate.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPredicate.java
new file mode 100644
index 0000000..6096452
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPredicate.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import org.gradle.api.Nullable;
+import org.gradle.model.internal.type.ModelType;
+
+/**
+ * A predicate that selects model nodes.
+ *
+ * <p>Defines a fixed set of criteria that a model node must match. A node is only selected when it matches <em>all</em> non-null criteria.</p>
+ */
+public abstract class ModelPredicate {
+    /**
+     * Returns the path of the node to select, or null if path is not relevant.
+     *
+     * <p>A node will be selected if its path equals the specified path.
+     */
+    @Nullable
+    public ModelPath getPath() {
+        return null;
+    }
+
+    /**
+     * Returns the parent path of the nodes to select, or null if parent is not relevant.
+     *
+     * <p>A node will be selected if its parent's path equals the specified path.
+     */
+    @Nullable
+    public ModelPath getParent() {
+        return null;
+    }
+
+    /**
+     * Return the path of the scope of the nodes to select, or null if scope is not relevant.
+     *
+     * <p>A node will be selected if its path or its parent's path equals the specified path.</p>
+     */
+    @Nullable
+    public ModelPath getAncestor() {
+        return null;
+    }
+
+    /**
+     * Returns the type of node to select, or null if type is not relevant.
+     */
+    @Nullable
+    public ModelType<?> getType() {
+        return null;
+    }
+
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPromise.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPromise.java
index 3483838..24ed62c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPromise.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPromise.java
@@ -27,9 +27,9 @@ public interface ModelPromise {
     // These methods return strings rather than types because it may be more complicated than what is able to be expressed via a ModelType.
     // Also, we don't want to encourage compatibility checking occurring by looping through such types as we have more options for optimising the compatibility check internally.
     // Also also, these methods are only called for reporting so values should typically not be precomputed.
-    Iterable<String> getWritableTypeDescriptions();
+    Iterable<String> getWritableTypeDescriptions(MutableModelNode node);
 
-    Iterable<String> getReadableTypeDescriptions();
+    Iterable<String> getReadableTypeDescriptions(MutableModelNode node);
 
     @Override
         // must implement logical equality
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java
index d636c20..9d039e4 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Objects;
 import net.jcip.annotations.ThreadSafe;
 import org.gradle.api.Nullable;
 import org.gradle.model.internal.type.ModelType;
@@ -33,15 +34,21 @@ import org.gradle.model.internal.type.ModelType;
  */
 @ThreadSafe
 public class ModelReference<T> {
-
+    @Nullable
     private final ModelPath path;
     private final ModelType<T> type;
+    @Nullable
+    private final ModelPath scope;
+    private final ModelNode.State state;
+    @Nullable
     private final String description;
 
-    private ModelReference(@Nullable ModelPath path, ModelType<T> type, String description) {
+    private ModelReference(@Nullable ModelPath path, ModelType<T> type, @Nullable ModelPath scope, ModelNode.State state, @Nullable String description) {
         this.path = path;
         this.type = type;
+        this.scope = scope;
         this.description = description;
+        this.state = state != null ? state : ModelNode.State.GraphClosed;
     }
 
     public static ModelReference<Object> any() {
@@ -49,7 +56,7 @@ public class ModelReference<T> {
     }
 
     public static <T> ModelReference<T> of(ModelPath path, ModelType<T> type, String description) {
-        return new ModelReference<T>(path, type, description);
+        return new ModelReference<T>(path, type, null, null, description);
     }
 
     public static <T> ModelReference<T> of(String path, ModelType<T> type, String description) {
@@ -57,7 +64,11 @@ public class ModelReference<T> {
     }
 
     public static <T> ModelReference<T> of(ModelPath path, ModelType<T> type) {
-        return new ModelReference<T>(path, type, null);
+        return new ModelReference<T>(path, type, null, null, null);
+    }
+
+    public static <T> ModelReference<T> of(ModelPath path, ModelType<T> type, ModelNode.State state) {
+        return new ModelReference<T>(path, type, null, state, null);
     }
 
     public static <T> ModelReference<T> of(ModelPath path, Class<T> type) {
@@ -101,6 +112,16 @@ public class ModelReference<T> {
         return path;
     }
 
+    /**
+     * Return the path of the scope of the node to select, or null if scope is not relevant.
+     *
+     * <p>A node will be selected if its path or its parent's path equals the specified path.</p>
+     */
+    @Nullable
+    public ModelPath getScope() {
+        return scope;
+    }
+
     @Nullable
     public String getDescription() {
         return description;
@@ -110,10 +131,32 @@ public class ModelReference<T> {
         return type;
     }
 
+    public ModelNode.State getState() {
+        return state;
+    }
+
     public boolean isUntyped() {
         return type.equals(ModelType.UNTYPED);
     }
 
+    public ModelReference<T> inScope(ModelPath scope) {
+        if (scope.equals(this.scope)) {
+            return this;
+        }
+        return new ModelReference<T>(path, type, scope, state, description);
+    }
+
+    public ModelReference<T> withPath(ModelPath path) {
+        return new ModelReference<T>(path, type, scope, state, description);
+    }
+
+    public ModelReference<T> atState(ModelNode.State state) {
+        if (state.equals(this.state)) {
+            return this;
+        }
+        return new ModelReference<T>(path, type, scope, state, description);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -124,25 +167,20 @@ public class ModelReference<T> {
         }
 
         ModelReference<?> that = (ModelReference<?>) o;
-
-        if (path == null) {
-            if (that.path == null) {
-                return type.equals(that.type);
-            }
-            return false;
-        }
-        return path.equals(that.path) && type.equals(that.type);
+        return Objects.equal(path, that.path) && Objects.equal(scope, that.scope) && type.equals(that.type) && state.equals(that.state);
     }
 
     @Override
     public int hashCode() {
-        int result = path.hashCode();
+        int result = path == null ? 0 : path.hashCode();
+        result = 31 * result + (scope == null ? 0 : scope.hashCode());
         result = 31 * result + type.hashCode();
+        result = 31 * result + state.hashCode();
         return result;
     }
 
     @Override
     public String toString() {
-        return "ModelReference{path=" + path + ", type=" + type + '}';
+        return "ModelReference{path=" + path + ", scope=" + scope + ", type=" + type + ", state=" + state + '}';
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrar.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrar.java
deleted file mode 100644
index 2031386..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrar.java
+++ /dev/null
@@ -1,29 +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.model.internal.core;
-
-public interface ModelRegistrar {
-
-    public ModelRegistrar replace(ModelCreator newCreator);
-
-    public ModelRegistrar create(ModelCreator creator);
-
-    public ModelRegistrar createOrReplace(ModelCreator newCreator);
-
-    public <T> ModelRegistrar configure(ModelActionRole role, ModelAction<T> action);
-
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewFactory.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewFactory.java
new file mode 100644
index 0000000..bdf1504
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+public interface ModelViewFactory<M> {
+    ModelView<M> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean writable);
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewState.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewState.java
new file mode 100644
index 0000000..47b735e
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelViewState.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+public interface ModelViewState {
+
+    void assertCanMutate();
+
+    void assertCanReadChildren();
+
+    boolean isCanMutate();
+
+    boolean isCanReadChildren();
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/MutableModelNode.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/MutableModelNode.java
index 6135b71..2f8e6c6 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/MutableModelNode.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/MutableModelNode.java
@@ -37,11 +37,15 @@ public interface MutableModelNode extends ModelNode {
 
     /**
      * Adds a reference node to the graph. A reference node is a node that refers to some other node elsewhere in the graph, similar to a symbolic link.
+     *
+     * The path returned by {@link ModelCreator#getPath()} is used to determine the name of the reference.
      */
     void addReference(ModelCreator creator);
 
     /**
      * Adds a node to the graph, linked from this node. The given creator is used to initialize the node when required.
+     *
+     * The path returned by {@link ModelCreator#getPath()} is used to determine the name of the link.
      */
     void addLink(ModelCreator creator);
 
@@ -57,19 +61,46 @@ public interface MutableModelNode extends ModelNode {
 
     /**
      * Applies an action to all nodes linked from this node.
+     *
+     * The type returned by {@link ModelAction#getSubject()} is used to filter the nodes, such that the action is applied only to those linked nodes with a view of the
+     * requested type available.
      */
     <T> void applyToAllLinks(ModelActionRole type, ModelAction<T> action);
 
     /**
+     * Applies an action to all nodes linked from this node, including all nodes transitively linked from this node.
+     *
+     * The type returned by {@link ModelAction#getSubject()} is used to filter the nodes, such that the action is applied only to those linked nodes with a view of the
+     * requested type available.
+     */
+    <T> void applyToAllLinksTransitive(ModelActionRole type, ModelAction<T> action);
+
+    /**
      * Applies an action to a linked node.
+     *
+     * The path returned by {@link ModelAction#getSubject()} is used to select the link to apply the action to.
      */
     <T> void applyToLink(ModelActionRole type, ModelAction<T> action);
 
+    /**
+     * Applies the given rules to a node linked from this node.
+     */
     void applyToLink(String name, Class<? extends RuleSource> rules);
 
+    /**
+     * Applies the given rules to this node.
+     */
     void applyToSelf(Class<? extends RuleSource> rules);
 
-    <T> void applyToLinks(Class<T> type, Class<? extends RuleSource> rules);
+    /**
+     * Applies the given rules to all nodes of the given type linked from this node.
+     */
+    void applyToLinks(ModelType<?> type, Class<? extends RuleSource> rules);
+
+    /**
+     * Applies the given rules to all nodes of the given type transitively linked from this node.
+     */
+    void applyToAllLinksTransitive(ModelType<?> type, Class<? extends RuleSource> rules);
 
     @Nullable
     MutableModelNode getLink(String name);
@@ -80,8 +111,12 @@ public interface MutableModelNode extends ModelNode {
 
     Iterable<? extends MutableModelNode> getLinks(ModelType<?> type);
 
+    <T> void setPrivateData(Class<? super T> type, T object);
+
     <T> void setPrivateData(ModelType<? super T> type, T object);
 
+    <T> T getPrivateData(Class<T> type);
+
     <T> T getPrivateData(ModelType<T> type);
 
     Object getPrivateData();
@@ -96,7 +131,11 @@ public interface MutableModelNode extends ModelNode {
      */
     void ensureUsable();
 
+    void realize();
+
     void setHidden(boolean hidden);
 
     boolean isMutable();
+
+    MutableModelNode getParent();
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NamedEntityInstantiators.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NamedEntityInstantiators.java
new file mode 100644
index 0000000..7df8c0b
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NamedEntityInstantiators.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+public class NamedEntityInstantiators {
+    public static <S> NamedEntityInstantiator<S> nonSubtype(Class<S> nonSubtype, final Class<?> baseClass) {
+        return new NamedEntityInstantiator<S>() {
+            @Override
+            public <D extends S> D create(String name, Class<D> type) {
+                throw new IllegalArgumentException(String.format("Cannot create an item of type %s as this is not a subtype of %s.", type.getName(), baseClass.getName()));
+            }
+        };
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NoInputsModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NoInputsModelAction.java
new file mode 100644
index 0000000..26c130c
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NoInputsModelAction.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.model.internal.core;
+
+import org.gradle.api.Action;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+
+import java.util.Collections;
+import java.util.List;
+
+public class NoInputsModelAction<T> implements ModelAction<T> {
+    private final ModelReference<T> subject;
+    private final Action<? super T> configAction;
+    private final ModelRuleDescriptor descriptor;
+
+    public NoInputsModelAction(ModelReference<T> subject, ModelRuleDescriptor descriptor, Action<? super T> configAction) {
+        this.subject = subject;
+        this.configAction = configAction;
+        this.descriptor = descriptor;
+    }
+
+    public static <T> ModelAction<T> of(ModelReference<T> reference, ModelRuleDescriptor descriptor, Action<? super T> configAction) {
+        return new NoInputsModelAction<T>(reference, descriptor, configAction);
+    }
+
+    @Override
+    public ModelReference<T> getSubject() {
+        return subject;
+    }
+
+    @Override
+    public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
+        configAction.execute(object);
+    }
+
+    @Override
+    public List<ModelReference<?>> getInputs() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public ModelRuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelMap.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelMap.java
new file mode 100644
index 0000000..1b113e0
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelMap.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.gradle.api.Action;
+import org.gradle.api.Nullable;
+import org.gradle.api.Transformer;
+import org.gradle.internal.Actions;
+import org.gradle.internal.BiAction;
+import org.gradle.internal.Cast;
+import org.gradle.model.ModelMap;
+import org.gradle.model.RuleSource;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
+import org.gradle.model.internal.manage.instance.ManagedInstance;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+public class NodeBackedModelMap<T> implements ModelMap<T>, ManagedInstance {
+
+    private final ModelType<T> elementType;
+    private final ModelRuleDescriptor sourceDescriptor;
+    private final MutableModelNode modelNode;
+    private final String description;
+    private final boolean eager;
+    private final ModelViewState viewState;
+    private final ChildNodeCreatorStrategy<? super T> creatorStrategy;
+
+    public NodeBackedModelMap(String description, ModelType<T> elementType, ModelRuleDescriptor sourceDescriptor, MutableModelNode modelNode, boolean eager, ModelViewState viewState, ChildNodeCreatorStrategy<? super T> creatorStrategy) {
+        this.description = description;
+        this.eager = eager;
+        this.viewState = viewState;
+        this.creatorStrategy = creatorStrategy;
+        this.elementType = elementType;
+        this.modelNode = modelNode;
+        this.sourceDescriptor = sourceDescriptor;
+    }
+
+    public NodeBackedModelMap(ModelType<T> type, ModelRuleDescriptor sourceDescriptor, MutableModelNode modelNode, boolean eager, ModelViewState viewState, ChildNodeCreatorStrategy<? super T> childNodeCreatorStrategy) {
+        this(derivedDescription(modelNode, type), type, sourceDescriptor, modelNode, eager, viewState, childNodeCreatorStrategy);
+    }
+
+    public static <T> ChildNodeCreatorStrategy<T> createUsingParentNode(final ModelType<T> baseItemModelType) {
+        return createUsingParentNode(new Transformer<NamedEntityInstantiator<T>, MutableModelNode>() {
+            @Override
+            public NamedEntityInstantiator<T> transform(MutableModelNode modelNode) {
+                return modelNode.getPrivateData(instantiatorTypeOf(baseItemModelType));
+            }
+        });
+    }
+
+    public static <T> ChildNodeCreatorStrategy<T> createUsingParentNode(final Transformer<? extends NamedEntityInstantiator<T>, ? super MutableModelNode> instantiatorTransform) {
+        return new ChildNodeCreatorStrategy<T>() {
+            @Override
+            public <S extends T> ModelCreator creator(final MutableModelNode parentNode, ModelRuleDescriptor sourceDescriptor, final ModelType<S> type, final String name) {
+                return ModelCreators.of(
+                    parentNode.getPath().child(name), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+                        @Override
+                        public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
+                            NamedEntityInstantiator<T> instantiator = instantiatorTransform.transform(parentNode);
+                            S item = instantiator.create(name, type.getConcreteClass());
+                            modelNode.setPrivateData(type, item);
+                        }
+                    })
+                    .withProjection(UnmanagedModelProjection.of(type))
+                    .descriptor(NestedModelRuleDescriptor.append(sourceDescriptor, "create(%s)", name))
+                    .build();
+            }
+        };
+    }
+
+    public static <T> ChildNodeCreatorStrategy<T> createUsingFactory(final ModelReference<? extends InstanceFactory<? super T, String>> factoryReference) {
+        return new ChildNodeCreatorStrategy<T>() {
+            @Override
+            public <S extends T> ModelCreator creator(final MutableModelNode parentNode, ModelRuleDescriptor sourceDescriptor, final ModelType<S> type, final String name) {
+                return ModelCreators.of(
+                    parentNode.getPath().child(name), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+                        @Override
+                        public void execute(MutableModelNode modelNode, List<ModelView<?>> modelViews) {
+                            InstanceFactory<? super T, String> instantiator = Cast.uncheckedCast(modelViews.get(0).getInstance());
+                            S item = instantiator.create(type.getConcreteClass(), modelNode, name);
+                            modelNode.setPrivateData(type, item);
+                        }
+                    })
+                    .inputs(factoryReference)
+                    .withProjection(UnmanagedModelProjection.of(type))
+                    .descriptor(NestedModelRuleDescriptor.append(sourceDescriptor, "create(%s)", name))
+                    .build();
+            }
+        };
+    }
+
+    public static <I> ModelType<NamedEntityInstantiator<I>> instantiatorTypeOf(ModelType<I> type) {
+        return new ModelType.Builder<NamedEntityInstantiator<I>>() {
+        }.where(
+            new ModelType.Parameter<I>() {
+            }, type
+        ).build();
+    }
+
+    @Override
+    public MutableModelNode getBackingNode() {
+        return modelNode;
+    }
+
+    public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
+        doFinalizeAll(ModelType.of(type), configAction);
+    }
+
+    @Override
+    public void afterEach(Action<? super T> configAction) {
+        doFinalizeAll(elementType, configAction);
+    }
+
+    @Override
+    public void all(final Action<? super T> configAction) {
+        viewState.assertCanMutate();
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "all()");
+        ModelReference<T> subject = ModelReference.of(elementType);
+        modelNode.applyToAllLinks(ModelActionRole.Mutate, NoInputsModelAction.of(subject, descriptor, configAction));
+    }
+
+    @Override
+    public void beforeEach(Action<? super T> configAction) {
+        doBeforeEach(elementType, configAction);
+    }
+
+    @Override
+    public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
+        doBeforeEach(ModelType.of(type), configAction);
+    }
+
+    @Override
+    public boolean containsKey(Object name) {
+        viewState.assertCanReadChildren();
+        return name instanceof String && modelNode.hasLink((String) name, elementType);
+    }
+
+    @Override
+    public boolean containsValue(Object item) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
+    @Override
+    public void create(final String name) {
+        doCreate(name, elementType, Actions.doNothing());
+    }
+
+    @Override
+    public void create(String name, Action<? super T> configAction) {
+        doCreate(name, elementType, configAction);
+    }
+
+    @Override
+    public <S extends T> void create(final String name, final Class<S> type) {
+        doCreate(name, ModelType.of(type), Actions.doNothing());
+    }
+
+    @Override
+    public <S extends T> void create(final String name, final Class<S> type, final Action<? super S> configAction) {
+        doCreate(name, ModelType.of(type), configAction);
+    }
+
+    private <S> void doBeforeEach(ModelType<S> type, Action<? super S> configAction) {
+        viewState.assertCanMutate();
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "beforeEach()");
+        ModelReference<S> subject = ModelReference.of(type);
+        modelNode.applyToAllLinks(ModelActionRole.Defaults, NoInputsModelAction.of(subject, descriptor, configAction));
+    }
+
+    private <S extends T> void doCreate(final String name, final ModelType<S> type, final Action<? super S> initAction) {
+        viewState.assertCanMutate();
+        ModelCreator creator = creatorStrategy.creator(modelNode, sourceDescriptor, type, name);
+        modelNode.addLink(creator);
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "%s.<init>", name);
+        modelNode.applyToLink(ModelActionRole.Initialize, NoInputsModelAction.of(ModelReference.of(creator.getPath(), type), descriptor, initAction));
+        if (eager) {
+            //noinspection ConstantConditions
+            modelNode.getLink(name).ensureUsable();
+        }
+    }
+
+    private <S> void doFinalizeAll(ModelType<S> type, Action<? super S> configAction) {
+        viewState.assertCanMutate();
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "afterEach()");
+        ModelReference<S> subject = ModelReference.of(type);
+        modelNode.applyToAllLinks(ModelActionRole.Finalize, NoInputsModelAction.of(subject, descriptor, configAction));
+    }
+
+    @Nullable
+    @Override
+    public T get(Object name) {
+        return get((String) name);
+    }
+
+    @Nullable
+    @Override
+    public T get(String name) {
+        viewState.assertCanReadChildren();
+
+        // TODO - lock this down
+        MutableModelNode link = modelNode.getLink(name);
+        if (link == null) {
+            return null;
+        }
+        link.ensureUsable();
+        if (viewState.isCanMutate()) {
+            return link.asWritable(elementType, sourceDescriptor, null).getInstance();
+        } else {
+            return link.asReadOnly(elementType, sourceDescriptor).getInstance();
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    @Override
+    public Set<String> keySet() {
+        viewState.assertCanReadChildren();
+        return modelNode.getLinkNames(elementType);
+    }
+
+    @Override
+    public void named(final String name, Action<? super T> configAction) {
+        viewState.assertCanMutate();
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "named(%s)", name);
+        ModelReference<T> subject = ModelReference.of(modelNode.getPath().child(name), elementType);
+        modelNode.applyToLink(ModelActionRole.Mutate, NoInputsModelAction.of(subject, descriptor, configAction));
+    }
+
+    @Override
+    public void named(String name, Class<? extends RuleSource> ruleSource) {
+        viewState.assertCanMutate();
+        modelNode.applyToLink(name, ruleSource);
+    }
+
+    @Override
+    public int size() {
+        viewState.assertCanReadChildren();
+        return modelNode.getLinkCount(elementType);
+    }
+
+    @Override
+    public String toString() {
+        return description;
+    }
+
+    private static String derivedDescription(ModelNode modelNode, ModelType<?> elementType) {
+        return ModelMap.class.getSimpleName() + '<' + elementType.getSimpleName() + "> '" + modelNode.getPath() + "'";
+    }
+
+    public <S extends T> ModelMap<S> toSubType(Class<S> type) {
+        ChildNodeCreatorStrategy<S> creatorStrategy = uncheckedCast(this.creatorStrategy);
+        return new NodeBackedModelMap<S>(ModelType.of(type), sourceDescriptor, modelNode, eager, viewState, creatorStrategy);
+    }
+
+    @Override
+    public Collection<T> values() {
+        viewState.assertCanReadChildren();
+        Iterable<T> values = Iterables.transform(keySet(), new Function<String, T>() {
+            public T apply(@Nullable String name) {
+                return get(name);
+            }
+        });
+        return Lists.newArrayList(values);
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Action<? super S> configAction) {
+        ModelRuleDescriptor descriptor = NestedModelRuleDescriptor.append(sourceDescriptor, "withType()");
+        ModelReference<S> subject = ModelReference.of(type);
+        viewState.assertCanMutate();
+        modelNode.applyToAllLinks(ModelActionRole.Mutate, NoInputsModelAction.of(subject, descriptor, configAction));
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
+        viewState.assertCanMutate();
+        modelNode.applyToLinks(ModelType.of(type), rules);
+    }
+
+    @Override
+    public <S> ModelMap<S> withType(Class<S> type) {
+        if (type.equals(elementType.getConcreteClass())) {
+            return uncheckedCast(this);
+        }
+
+        if (elementType.getConcreteClass().isAssignableFrom(type)) {
+            Class<? extends T> castType = uncheckedCast(type);
+            ModelMap<? extends T> subType = toSubType(castType);
+            return uncheckedCast(subType);
+        }
+
+        return new NodeBackedModelMap<S>(ModelType.of(type), sourceDescriptor, modelNode, eager, viewState, new ChildNodeCreatorStrategy<S>() {
+            @Override
+            public <D extends S> ModelCreator creator(MutableModelNode parentNode, ModelRuleDescriptor sourceDescriptor, ModelType<D> type, String name) {
+                throw new IllegalArgumentException(String.format("Cannot create an item of type %s as this is not a subtype of %s.", type, elementType.toString()));
+            }
+        });
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelSet.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelSet.java
new file mode 100644
index 0000000..4a1ca5d
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/NodeBackedModelSet.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.model.ModelSet;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
+import org.gradle.model.internal.manage.instance.ManagedInstance;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public class NodeBackedModelSet<T> implements ModelSet<T>, ManagedInstance {
+
+    private final String toString;
+    private final ModelType<T> elementType;
+    private final ModelRuleDescriptor descriptor;
+    private final MutableModelNode modelNode;
+    private final ModelViewState state;
+    private final ChildNodeCreatorStrategy<T> creatorStrategy;
+    private final ModelReference<T> elementTypeReference;
+
+    private Collection<T> elements;
+
+    public NodeBackedModelSet(String toString, ModelType<T> elementType, ModelRuleDescriptor descriptor, MutableModelNode modelNode, ModelViewState state, ChildNodeCreatorStrategy<T> creatorStrategy) {
+        this.toString = toString;
+        this.elementType = elementType;
+        this.elementTypeReference = ModelReference.of(elementType);
+        this.descriptor = descriptor;
+        this.modelNode = modelNode;
+        this.state = state;
+        this.creatorStrategy = creatorStrategy;
+    }
+
+    @Override
+    public MutableModelNode getBackingNode() {
+        return modelNode;
+    }
+
+    @Override
+    public String toString() {
+        return toString;
+    }
+
+    @Override
+    public void create(final Action<? super T> action) {
+        state.assertCanMutate();
+        String name = String.valueOf(modelNode.getLinkCount(elementType));
+        modelNode.addLink(creatorStrategy.creator(modelNode, descriptor, elementType, name));
+        modelNode.applyToLink(ModelActionRole.Initialize, NoInputsModelAction.of(
+                ModelReference.of(modelNode.getPath().child(name), elementType), NestedModelRuleDescriptor.append(descriptor, "create()"), action)
+        );
+    }
+
+    @Override
+    public void afterEach(Action<? super T> configAction) {
+        state.assertCanMutate();
+        modelNode.applyToAllLinks(ModelActionRole.Finalize, NoInputsModelAction.of(elementTypeReference, NestedModelRuleDescriptor.append(descriptor, "afterEach()"), configAction));
+    }
+
+    @Override
+    public void beforeEach(Action<? super T> configAction) {
+        state.assertCanMutate();
+        modelNode.applyToAllLinks(ModelActionRole.Defaults, NoInputsModelAction.of(elementTypeReference, NestedModelRuleDescriptor.append(descriptor, "afterEach()"), configAction));
+    }
+
+    @Override
+    public int size() {
+        state.assertCanReadChildren();
+        return modelNode.getLinkCount(elementType);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return getElements().contains(o);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return getElements().iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return getElements().toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return getElements().toArray(a);
+    }
+
+    @Override
+    public boolean add(T e) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return getElements().containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends T> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    // TODO - mix this in using decoration. Also validate closure parameter types, if declared
+    public void create(Closure<?> closure) {
+        create(ClosureBackedAction.of(closure));
+    }
+
+    public void afterEach(Closure<?> closure) {
+        afterEach(ClosureBackedAction.of(closure));
+    }
+
+    public void beforeEach(Closure<?> closure) {
+        beforeEach(ClosureBackedAction.of(closure));
+    }
+
+    private Collection<T> getElements() {
+        state.assertCanReadChildren();
+        if (elements == null) {
+            elements = Lists.newArrayList(
+                Iterables.transform(modelNode.getLinks(elementType), new Function<MutableModelNode, T>() {
+                    @Override
+                    public T apply(MutableModelNode input) {
+                        return input.asReadOnly(elementType, descriptor).getInstance();
+                    }
+                })
+            );
+        }
+        return elements;
+    }
+
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ProjectionBackedModelCreator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ProjectionBackedModelCreator.java
index 03ca04a..c50c125 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ProjectionBackedModelCreator.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ProjectionBackedModelCreator.java
@@ -29,17 +29,17 @@ public class ProjectionBackedModelCreator implements ModelCreator {
     private final boolean ephemeral;
     private final boolean hidden;
     private final ModelProjection projection;
-    private final List<? extends ModelReference<?>> inputs;
+    private final List<ModelReference<?>> inputs;
     private final BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer;
 
     public ProjectionBackedModelCreator(
-            ModelPath path,
-            ModelRuleDescriptor descriptor,
-            boolean ephemeral,
-            boolean hidden,
-            List<? extends ModelReference<?>> inputs,
-            ModelProjection projection,
-            BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer
+        ModelPath path,
+        ModelRuleDescriptor descriptor,
+        boolean ephemeral,
+        boolean hidden,
+        List<ModelReference<?>> inputs,
+        ModelProjection projection,
+        BiAction<? super MutableModelNode, ? super List<ModelView<?>>> initializer
     ) {
         this.path = path;
         this.descriptor = descriptor;
@@ -72,7 +72,7 @@ public class ProjectionBackedModelCreator implements ModelCreator {
         return ephemeral;
     }
 
-    public List<? extends ModelReference<?>> getInputs() {
+    public List<ModelReference<?>> getInputs() {
         return inputs;
     }
 
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/SpecializedModelMapProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/SpecializedModelMapProjection.java
new file mode 100644
index 0000000..41197bc
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/SpecializedModelMapProjection.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Optional;
+import org.gradle.api.Nullable;
+import org.gradle.internal.Cast;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Should be used along with {@code PolymorphicModelMapProjection}.
+ */
+public class SpecializedModelMapProjection<P extends ModelMap<E>, E> implements ModelProjection {
+
+    private final ModelType<P> publicType;
+    private final ModelType<E> elementType;
+
+    private final Class<? extends P> viewImpl;
+    private final ChildNodeCreatorStrategy<E> creatorStrategy;
+
+    public SpecializedModelMapProjection(ModelType<P> publicType, ModelType<E> elementType, Class<? extends P> viewImpl, ChildNodeCreatorStrategy<E> creatorStrategy) {
+        this.publicType = publicType;
+        this.elementType = elementType;
+        this.viewImpl = viewImpl;
+        this.creatorStrategy = creatorStrategy;
+    }
+
+    @Override
+    public Iterable<String> getReadableTypeDescriptions(MutableModelNode node) {
+        return getWritableTypeDescriptions(node);
+    }
+
+    @Override
+    public Iterable<String> getWritableTypeDescriptions(MutableModelNode node) {
+        return Collections.singleton(publicType.toString());
+    }
+
+    @Nullable
+    @Override
+    public <T> ModelView<? extends T> asReadOnly(ModelType<T> type, MutableModelNode node, @Nullable ModelRuleDescriptor ruleDescriptor) {
+        if (canBeViewedAsReadOnly(type)) {
+            return Cast.uncheckedCast(toView(node, ruleDescriptor, false));
+        } else {
+            return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public <T> ModelView<? extends T> asWritable(ModelType<T> type, MutableModelNode node, ModelRuleDescriptor ruleDescriptor, List<ModelView<?>> implicitDependencies) {
+        if (canBeViewedAsWritable(type)) {
+            return Cast.uncheckedCast(toView(node, ruleDescriptor, true));
+        } else {
+            return null;
+        }
+    }
+
+    private ModelView<P> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean mutable) {
+        DefaultModelViewState state = new DefaultModelViewState(publicType, ruleDescriptor, mutable, true);
+        String description = publicType.getSimpleName() + " '" + modelNode.getPath() + "'";
+        ModelMap<E> rawView = new NodeBackedModelMap<E>(description, elementType, ruleDescriptor, modelNode, false, state, creatorStrategy);
+        P instance = DirectInstantiator.instantiate(viewImpl, rawView);
+        return InstanceModelView.of(modelNode.getPath(), publicType, instance, state.closer());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        SpecializedModelMapProjection<?, ?> that = (SpecializedModelMapProjection<?, ?>) o;
+        return publicType.equals(that.publicType) && viewImpl.equals(that.viewImpl);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + publicType.hashCode();
+        result = 31 * result + viewImpl.hashCode();
+        return result;
+    }
+
+    @Override
+    public <T> boolean canBeViewedAsWritable(ModelType<T> targetType) {
+        return targetType.equals(publicType) || targetType.equals(ModelType.of(Object.class));
+    }
+
+    @Override
+    public <T> boolean canBeViewedAsReadOnly(ModelType<T> targetType) {
+        return canBeViewedAsWritable(targetType);
+    }
+
+    @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java
index 0b5ff21..6b0ca07 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java
@@ -67,7 +67,7 @@ public abstract class TypeCompatibilityModelProjectionSupport<M> implements Mode
 
     protected abstract ModelView<M> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean writable);
 
-    public Iterable<String> getWritableTypeDescriptions() {
+    public Iterable<String> getWritableTypeDescriptions(MutableModelNode node) {
         if (canBeViewedAsWritable) {
             return Collections.singleton(description(type));
         } else {
@@ -75,7 +75,7 @@ public abstract class TypeCompatibilityModelProjectionSupport<M> implements Mode
         }
     }
 
-    public Iterable<String> getReadableTypeDescriptions() {
+    public Iterable<String> getReadableTypeDescriptions(MutableModelNode node) {
         if (canBeViewedAsReadOnly) {
             return Collections.singleton(description(type));
         } else {
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypedModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypedModelProjection.java
new file mode 100644
index 0000000..2106666
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypedModelProjection.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Optional;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.type.ModelType;
+
+public class TypedModelProjection<M> extends TypeCompatibilityModelProjectionSupport<M> {
+
+    private final ModelViewFactory<M> viewFactory;
+
+    public static <M> ModelProjection of(ModelType<M> type, ModelViewFactory<M> viewFactory) {
+        return new TypedModelProjection<M>(type, viewFactory, true, true);
+    }
+
+    public TypedModelProjection(ModelType<M> type, ModelViewFactory<M> viewFactory, boolean canBeViewedAsReadOnly, boolean canBeViewedAsWritable) {
+        super(type, canBeViewedAsReadOnly, canBeViewedAsWritable);
+        this.viewFactory = viewFactory;
+    }
+
+    @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
+
+    @Override
+    protected ModelView<M> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean writable) {
+        return viewFactory.toView(modelNode, ruleDescriptor, writable);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        //noinspection EqualsBetweenInconvertibleTypes
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        TypedModelProjection<?> that = (TypedModelProjection<?>) o;
+        return viewFactory.equals(that.viewFactory);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + viewFactory.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java
index b5ffc27..4a2117e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java
@@ -16,13 +16,23 @@
 
 package org.gradle.model.internal.core;
 
+import com.google.common.base.Optional;
 import net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.type.ModelType;
 
 @ThreadSafe
 public class UnmanagedModelProjection<M> extends TypeCompatibilityModelProjectionSupport<M> {
 
+    public static <M> ModelProjection of(ModelType<M> type) {
+        return new UnmanagedModelProjection<M>(type);
+    }
+
+    public static <M> ModelProjection of(Class<M> type) {
+        return of(ModelType.of(type));
+    }
+
     public UnmanagedModelProjection(ModelType<M> type) {
         super(type, true, true);
     }
@@ -37,4 +47,13 @@ public class UnmanagedModelProjection<M> extends TypeCompatibilityModelProjectio
         return InstanceModelView.of(modelNode.getPath(), getType(), instance);
     }
 
+    @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        ModelView<?> modelView = this.asReadOnly(ModelType.untyped(), modelNodeInternal, null);
+        Object instance = modelView.getInstance();
+        if (null != instance && !JavaReflectionUtil.hasDefaultToString(instance)) {
+            return Optional.fromNullable(instance.toString());
+        }
+        return Optional.absent();
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java
index e44f686..68412b8 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java
@@ -32,6 +32,10 @@ public class NestedModelRuleDescriptor extends AbstractModelRuleDescriptor {
         this.child = child;
     }
 
+    public NestedModelRuleDescriptor(ModelRuleDescriptor parent, String child) {
+        this(parent, new SimpleModelRuleDescriptor(child));
+    }
+
     public void describeTo(Appendable appendable) {
         parent.describeTo(appendable);
         try {
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactory.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactory.java
new file mode 100644
index 0000000..9263a83
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core.rule.describe;
+
+import org.gradle.api.Transformer;
+
+public class StandardDescriptorFactory implements Transformer<String, String> {
+    private final String descriptor;
+
+    public StandardDescriptorFactory(String descriptor) {
+        this.descriptor = descriptor;
+    }
+    public StandardDescriptorFactory(ModelRuleDescriptor descriptor) {
+        StringBuilder builder = new StringBuilder();
+        descriptor.describeTo(builder);
+        this.descriptor = builder.toString();
+    }
+
+
+    @Override
+    public String transform(String s) {
+        return descriptor + '.' + s + "()";
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultModelCreatorFactory.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultModelCreatorFactory.java
index 43c6fe8..18908e8 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultModelCreatorFactory.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultModelCreatorFactory.java
@@ -17,66 +17,246 @@
 package org.gradle.model.internal.inspect;
 
 import org.gradle.api.Action;
+import org.gradle.api.Named;
 import org.gradle.api.Nullable;
 import org.gradle.internal.BiAction;
+import org.gradle.internal.Cast;
+import org.gradle.model.ModelSet;
+import org.gradle.model.collection.ManagedSet;
+import org.gradle.model.collection.internal.ModelMapModelProjection;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
 import org.gradle.model.internal.manage.instance.ManagedProxyFactory;
 import org.gradle.model.internal.manage.projection.ManagedModelProjection;
-import org.gradle.model.internal.manage.projection.ManagedSetModelProjection;
 import org.gradle.model.internal.manage.schema.ModelCollectionSchema;
 import org.gradle.model.internal.manage.schema.ModelSchema;
 import org.gradle.model.internal.manage.schema.ModelSchemaStore;
 import org.gradle.model.internal.manage.schema.ModelStructSchema;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
 
+import java.util.Collections;
 import java.util.List;
 
 public class DefaultModelCreatorFactory implements ModelCreatorFactory {
     private final ModelSchemaStore schemaStore;
     private final ManagedProxyFactory proxyFactory;
+    private ManagedChildNodeCreatorStrategy<?> managedChildCreatorStrategy;
 
     public DefaultModelCreatorFactory(ModelSchemaStore schemaStore) {
         this.schemaStore = schemaStore;
         this.proxyFactory = new ManagedProxyFactory();
+        this.managedChildCreatorStrategy = new ManagedChildNodeCreatorStrategy<Object>(this, schemaStore);
     }
 
     @Override
     public <T> ModelCreator creator(ModelRuleDescriptor descriptor, ModelPath path, ModelSchema<T> schema) {
-        ModelReference<T> modelReference = ModelReference.of(path, schema.getType());
-        return creator(descriptor, modelReference, schema, null);
+        return creator(descriptor, path, schema, (ModelAction<T>) null);
     }
 
     @Override
     public <T> ModelCreator creator(ModelRuleDescriptor descriptor, ModelPath path, ModelSchema<T> schema, Action<? super T> initializer) {
         ModelReference<T> modelReference = ModelReference.of(path, schema.getType());
-        ModelAction<T> modelAction = new ActionBackedModelAction<T>(modelReference, descriptor, initializer);
-        return creator(descriptor, modelReference, schema, modelAction);
+        ModelAction<T> modelAction = new NoInputsModelAction<T>(modelReference, descriptor, initializer);
+        return creator(descriptor, path, schema, modelAction);
     }
 
     @Override
     public <T> ModelCreator creator(ModelRuleDescriptor descriptor, ModelPath path, ModelSchema<T> schema, List<ModelReference<?>> initializerInputs, BiAction<? super T, ? super List<ModelView<?>>> initializer) {
         ModelReference<T> modelReference = ModelReference.of(path, schema.getType());
-        ModelAction<T> modelAction = new BiActionBackedModelAction<T>(modelReference, descriptor, initializerInputs, initializer);
-        return creator(descriptor, modelReference, schema, modelAction);
+        ModelAction<T> modelAction = new InputUsingModelAction<T>(modelReference, descriptor, initializerInputs, initializer);
+        return creator(descriptor, path, schema, modelAction);
     }
 
-    private <T> ModelCreator creator(ModelRuleDescriptor descriptor, ModelReference<T> modelReference, ModelSchema<T> schema, @Nullable ModelAction<T> initializer) {
-        // TODO reuse pooled projections
+    private <T> ModelCreator creator(ModelRuleDescriptor descriptor, ModelPath path, ModelSchema<T> schema, @Nullable ModelAction<T> initializer) {
+        ModelType<T> type = schema.getType();
+        ModelCreators.Builder builder;
+
         if (schema instanceof ModelCollectionSchema) {
+            builder = ModelCreators.of(path);
             ModelCollectionSchema<T> collectionSchema = (ModelCollectionSchema<T>) schema;
-            ModelSchema<?> elementSchema = schemaStore.getSchema(collectionSchema.getElementType());
-            return ModelCreators.of(modelReference, new ManagedSetInitializer<T>(initializer))
-                    .withProjection(ManagedSetModelProjection.of(elementSchema, this))
-                    .descriptor(descriptor)
-                    .build();
-        }
-        if (schema instanceof ModelStructSchema) {
+            ModelType<?> elementType = collectionSchema.getElementType();
+            addCollectionProjection(builder, collectionSchema, elementType);
+        } else if (schema instanceof ModelStructSchema) {
             ModelStructSchema<T> structSchema = (ModelStructSchema<T>) schema;
-            return ModelCreators.of(modelReference, new ManagedModelInitializer<T>(descriptor, structSchema, schemaStore, this, initializer))
-                    .withProjection(new ManagedModelProjection<T>(structSchema, schemaStore, proxyFactory))
-                    .descriptor(descriptor)
-                    .build();
+            builder = ModelCreators.of(path, new ManagedModelInitializer<T>(descriptor, structSchema, schemaStore, this))
+                .withProjection(new ManagedModelProjection<T>(structSchema, schemaStore, proxyFactory));
+        } else {
+            throw new IllegalArgumentException("Don't know how to create model element from schema for " + type);
+        }
+
+        builder.descriptor(descriptor);
+
+        if (schema.getKind() == ModelSchema.Kind.STRUCT && Named.class.isAssignableFrom(type.getRawClass())) {
+            builder.action(ModelActionRole.Initialize, new NamedInitializer(path, descriptor));
+        }
+        if (initializer != null) {
+            builder.action(ModelActionRole.Initialize, initializer);
+        }
+
+        return builder.build();
+    }
+
+    @SuppressWarnings("deprecation")
+    private <T, E> void addCollectionProjection(ModelCreators.Builder builder, ModelCollectionSchema<T> collectionSchema, ModelType<E> elementType) {
+        if (collectionSchema.isMap()) {
+            builder.withProjection(modelMapProjection(elementType));
+        } else {
+            Class<? super T> setType = collectionSchema.getType().getRawClass();
+            if (setType.equals(ModelSet.class)) {
+                builder.withProjection(
+                    TypedModelProjection.of(ModelTypes.modelSet(elementType), modelSetFactory(elementType))
+                );
+            } else if (setType.equals(ManagedSet.class)) {
+                builder.withProjection(
+                    TypedModelProjection.of(ModelTypes.managedSet(elementType), managedSetFactory(elementType))
+                );
+            } else {
+                throw new IllegalStateException("Expected ModelSet or ManagedSet");
+            }
+        }
+    }
+
+    private <T> ModelProjection modelMapProjection(ModelType<T> elementType) {
+        return ModelMapModelProjection.managed(elementType, childCreator());
+    }
+
+    private <T> ChildNodeCreatorStrategy<T> childCreator() {
+        return Cast.uncheckedCast(managedChildCreatorStrategy);
+    }
+
+    private <T> ChildNodeCreatorStrategy<T> childCreator(@SuppressWarnings("UnusedParameters") ModelType<T> modelType) {
+        return Cast.uncheckedCast(managedChildCreatorStrategy);
+    }
+
+    private static class ManagedChildNodeCreatorStrategy<T> implements ChildNodeCreatorStrategy<T> {
+        private final ModelCreatorFactory modelCreatorFactory;
+        private final ModelSchemaStore modelSchemaStore;
+
+        public ManagedChildNodeCreatorStrategy(ModelCreatorFactory modelCreatorFactory, ModelSchemaStore modelSchemaStore) {
+            this.modelCreatorFactory = modelCreatorFactory;
+            this.modelSchemaStore = modelSchemaStore;
+        }
+
+        @Override
+        public <S extends T> ModelCreator creator(MutableModelNode parentNode, ModelRuleDescriptor sourceDescriptor, ModelType<S> type, final String name) {
+            ModelPath childPath = parentNode.getPath().child(name);
+            return modelCreatorFactory.creator(sourceDescriptor, childPath, modelSchemaStore.getSchema(type));
+        }
+    }
+
+    private static class NamedInitializer implements ModelAction<Object> {
+
+        private final ModelPath modelPath;
+        private final ModelRuleDescriptor parentDescriptor;
+
+        public NamedInitializer(ModelPath modelPath, ModelRuleDescriptor parentDescriptor) {
+            this.modelPath = modelPath;
+            this.parentDescriptor = parentDescriptor;
+        }
+
+        @Override
+        public ModelReference<Object> getSubject() {
+            return ModelReference.of(modelPath);
+        }
+
+        @Override
+        public void execute(MutableModelNode modelNode, Object object, List<ModelView<?>> inputs) {
+            MutableModelNode nameLink = modelNode.getLink("name");
+            if (nameLink == null) {
+                throw new IllegalStateException("expected name node for " + modelNode.getPath());
+            }
+            nameLink.setPrivateData(ModelType.of(String.class), modelNode.getPath().getName());
+        }
+
+        @Override
+        public List<ModelReference<?>> getInputs() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public ModelRuleDescriptor getDescriptor() {
+            return new NestedModelRuleDescriptor(parentDescriptor, "<set name>");
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private <T> ModelViewFactory<ManagedSet<T>> managedSetFactory(final ModelType<T> elementType) {
+        return new ManagedSetModelViewFactory<T>(elementType);
+    }
+
+    private <T> ModelViewFactory<ModelSet<T>> modelSetFactory(final ModelType<T> elementType) {
+        return new ModelSetModelViewFactory<T>(elementType);
+    }
+
+    private class ManagedSetModelViewFactory<T> implements ModelViewFactory<ManagedSet<T>> {
+        private final ModelType<T> elementType;
+
+        public ManagedSetModelViewFactory(ModelType<T> elementType) {
+            this.elementType = elementType;
+        }
+
+        @Override
+        public ModelView<ManagedSet<T>> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean writable) {
+            ModelType<ManagedSet<T>> setType = ModelTypes.managedSet(elementType);
+            DefaultModelViewState state = new DefaultModelViewState(setType, ruleDescriptor, writable, !writable);
+            NodeBackedModelSet<T> set = new NodeBackedModelSet<T>(setType.toString() + " '" + modelNode.getPath() + "'", elementType, ruleDescriptor, modelNode, state, childCreator(elementType));
+            return new InstanceModelView<ManagedSet<T>>(modelNode.getPath(), setType, set, state.closer());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            ManagedSetModelViewFactory<?> that = (ManagedSetModelViewFactory<?>) o;
+            return elementType.equals(that.elementType);
+
+        }
+
+        @Override
+        public int hashCode() {
+            return elementType.hashCode();
+        }
+    }
+
+    private class ModelSetModelViewFactory<T> implements ModelViewFactory<ModelSet<T>> {
+        private final ModelType<T> elementType;
+
+        public ModelSetModelViewFactory(ModelType<T> elementType) {
+            this.elementType = elementType;
+        }
+
+        @Override
+        public ModelView<ModelSet<T>> toView(MutableModelNode modelNode, ModelRuleDescriptor ruleDescriptor, boolean writable) {
+            ModelType<ModelSet<T>> setType = ModelTypes.modelSet(elementType);
+            DefaultModelViewState state = new DefaultModelViewState(setType, ruleDescriptor, writable, !writable);
+            NodeBackedModelSet<T> set = new NodeBackedModelSet<T>(setType.toString() + " '" + modelNode.getPath() + "'", elementType, ruleDescriptor, modelNode, state, childCreator(elementType));
+            return new InstanceModelView<ModelSet<T>>(modelNode.getPath(), setType, set, state.closer());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            ModelSetModelViewFactory<?> that = (ModelSetModelViewFactory<?>) o;
+            return elementType.equals(that.elementType);
+
+        }
+
+        @Override
+        public int hashCode() {
+            return elementType.hashCode();
         }
-        throw new IllegalArgumentException("Don't know how to create model element from schema for " + schema.getType());
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedModelInitializer.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedModelInitializer.java
index 61d7a5b..e3eaf5e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedModelInitializer.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedModelInitializer.java
@@ -29,26 +29,21 @@ import org.gradle.model.internal.type.ModelType;
 public class ManagedModelInitializer<T> implements BiAction<MutableModelNode, Object> {
 
     private final ModelStructSchema<T> modelSchema;
-    private final ModelAction<T> initializer;
     private final ModelRuleDescriptor descriptor;
     private final ModelSchemaStore schemaStore;
     private final ModelCreatorFactory modelCreatorFactory;
 
-    public ManagedModelInitializer(ModelRuleDescriptor descriptor, ModelStructSchema<T> modelSchema, ModelSchemaStore schemaStore, ModelCreatorFactory modelCreatorFactory, ModelAction<T> initializer) {
+    public ManagedModelInitializer(ModelRuleDescriptor descriptor, ModelStructSchema<T> modelSchema, ModelSchemaStore schemaStore, ModelCreatorFactory modelCreatorFactory) {
         this.descriptor = descriptor;
         this.schemaStore = schemaStore;
         this.modelCreatorFactory = modelCreatorFactory;
         this.modelSchema = modelSchema;
-        this.initializer = initializer;
     }
 
     public void execute(MutableModelNode modelNode, Object object) {
         for (ModelProperty<?> property : modelSchema.getProperties().values()) {
             addPropertyLink(modelNode, property);
         }
-        if (initializer != null) {
-            modelNode.applyToSelf(ModelActionRole.Initialize, initializer);
-        }
     }
 
     private <P> void addPropertyLink(MutableModelNode modelNode, ModelProperty<P> property) {
@@ -61,16 +56,16 @@ public class ManagedModelInitializer<T> implements BiAction<MutableModelNode, Ob
                 modelNode.addLink(creator);
             } else {
                 ModelProjection projection = new UnmanagedModelProjection<P>(propertyType, true, true);
-                ModelCreator creator = ModelCreators.of(ModelReference.of(modelNode.getPath().child(property.getName()), propertyType), BiActions.doNothing())
-                        .withProjection(projection)
-                        .descriptor(descriptor).build();
+                ModelCreator creator = ModelCreators.of(modelNode.getPath().child(property.getName()), BiActions.doNothing())
+                    .withProjection(projection)
+                    .descriptor(descriptor).build();
                 modelNode.addReference(creator);
             }
         } else {
             ModelProjection projection = new UnmanagedModelProjection<P>(propertyType, true, true);
-            ModelCreator creator = ModelCreators.of(ModelReference.of(modelNode.getPath().child(property.getName()), propertyType), BiActions.doNothing())
-                    .withProjection(projection)
-                    .descriptor(descriptor).build();
+            ModelCreator creator = ModelCreators.of(modelNode.getPath().child(property.getName()), BiActions.doNothing())
+                .withProjection(projection)
+                .descriptor(descriptor).build();
             modelNode.addLink(creator);
         }
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedSetInitializer.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedSetInitializer.java
deleted file mode 100644
index 5cea45c..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ManagedSetInitializer.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.model.internal.inspect;
-
-import org.gradle.internal.BiAction;
-import org.gradle.model.internal.core.ModelAction;
-import org.gradle.model.internal.core.ModelActionRole;
-import org.gradle.model.internal.core.ModelView;
-import org.gradle.model.internal.core.MutableModelNode;
-
-import java.util.List;
-
-class ManagedSetInitializer<T> implements BiAction<MutableModelNode, List<ModelView<?>>> {
-    private final ModelAction<T> modelAction;
-
-    public ManagedSetInitializer(ModelAction<T> modelAction) {
-        this.modelAction = modelAction;
-    }
-
-    @Override
-    public void execute(MutableModelNode modelNode, List<ModelView<?>> inputs) {
-        if (modelAction != null) {
-            modelNode.applyToSelf(ModelActionRole.Initialize, modelAction);
-        }
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodBackedModelAction.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodBackedModelAction.java
index d4e7c56..514c3e2 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodBackedModelAction.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodBackedModelAction.java
@@ -62,4 +62,9 @@ class MethodBackedModelAction<T> implements ModelAction<T> {
         }
         ruleInvoker.invoke(args);
     }
+
+    @Override
+    public String toString() {
+        return "MethodBackedModelAction{descriptor=" + descriptor + ", subject=" + subject + ", inputs=" + inputs + '}';
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/UnmanagedModelCreationRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/UnmanagedModelCreationRuleExtractor.java
index e9bcfc9..9a0a1a9 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/UnmanagedModelCreationRuleExtractor.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/UnmanagedModelCreationRuleExtractor.java
@@ -46,7 +46,7 @@ public class UnmanagedModelCreationRuleExtractor extends AbstractModelCreationRu
         ModelRuleDescriptor descriptor = ruleDefinition.getDescriptor();
 
         BiAction<MutableModelNode, List<ModelView<?>>> transformer = new ModelRuleInvokerBackedTransformer<R>(returnType, ruleDefinition.getRuleInvoker(), descriptor);
-        ModelCreator modelCreator = ModelCreators.of(ModelReference.of(ModelPath.path(modelName), returnType), transformer)
+        ModelCreator modelCreator = ModelCreators.of(ModelPath.path(modelName), transformer)
                 .withProjection(new UnmanagedModelProjection<R>(returnType, true, true))
                 .descriptor(descriptor)
                 .inputs(references)
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedModelProjection.java
index b485302..7b48c20 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedModelProjection.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedModelProjection.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.manage.projection;
 
+import com.google.common.base.Optional;
 import org.gradle.internal.Cast;
 import org.gradle.model.ModelViewClosedException;
 import org.gradle.model.internal.core.ModelPath;
@@ -166,6 +167,11 @@ public class ManagedModelProjection<M> extends TypeCompatibilityModelProjectionS
     }
 
     @Override
+    public Optional<String> getValueDescription(MutableModelNode modelNodeInternal) {
+        return Optional.absent();
+    }
+
+    @Override
     public int hashCode() {
         return super.hashCode();
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedSetModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedSetModelProjection.java
deleted file mode 100644
index 438d616..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/projection/ManagedSetModelProjection.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * 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.model.internal.manage.projection;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.model.ModelViewClosedException;
-import org.gradle.model.WriteOnlyModelViewException;
-import org.gradle.model.collection.ManagedSet;
-import org.gradle.model.internal.core.*;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
-import org.gradle.model.internal.manage.instance.ManagedInstance;
-import org.gradle.model.internal.manage.schema.ModelSchema;
-import org.gradle.model.internal.type.ModelType;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class ManagedSetModelProjection<I> extends TypeCompatibilityModelProjectionSupport<ManagedSet<I>> {
-    private final ModelCreatorFactory modelCreatorFactory;
-    private final ModelSchema<I> elementSchema;
-
-    private ManagedSetModelProjection(ModelSchema<I> elementSchema, ModelCreatorFactory modelCreatorFactory) {
-        super(typeOf(elementSchema.getType()), true, true);
-        this.elementSchema = elementSchema;
-        this.modelCreatorFactory = modelCreatorFactory;
-    }
-
-    public static <I> ManagedSetModelProjection<I> of(ModelSchema<I> elementSchema, ModelCreatorFactory modelCreatorFactory) {
-        return new ManagedSetModelProjection<I>(elementSchema, modelCreatorFactory);
-    }
-
-    public static <I> ModelType<ManagedSet<I>> typeOf(ModelType<I> elementType) {
-        return new ModelType.Builder<ManagedSet<I>>() {
-        }.where(new ModelType.Parameter<I>() {
-        }, elementType).build();
-    }
-
-    @Override
-    protected ModelView<ManagedSet<I>> toView(final MutableModelNode modelNode, final ModelRuleDescriptor ruleDescriptor, final boolean writable) {
-        return new ManagedSetModelView<I>(getType(), elementSchema, modelNode, writable, ruleDescriptor, modelCreatorFactory);
-    }
-
-    private static class ManagedSetModelView<I> implements ModelView<ManagedSet<I>> {
-        private final ModelType<ManagedSet<I>> type;
-        private final ModelSchema<I> elementSchema;
-        private final MutableModelNode modelNode;
-        private final boolean writable;
-        private final ModelRuleDescriptor ruleDescriptor;
-        private final ModelCreatorFactory modelCreatorFactory;
-        private boolean closed;
-        private Set<I> elementViews;
-        private final ModelReference<I> elementReference;
-
-        public ManagedSetModelView(ModelType<ManagedSet<I>> type, ModelSchema<I> elementSchema, MutableModelNode modelNode, boolean writable, ModelRuleDescriptor ruleDescriptor, ModelCreatorFactory modelCreatorFactory) {
-            this.type = type;
-            this.elementSchema = elementSchema;
-            this.modelNode = modelNode;
-            this.writable = writable;
-            this.ruleDescriptor = ruleDescriptor;
-            this.modelCreatorFactory = modelCreatorFactory;
-            this.elementReference = ModelReference.of(elementSchema.getType());
-        }
-
-        @Override
-        public ModelPath getPath() {
-            return modelNode.getPath();
-        }
-
-        @Override
-        public ModelType<ManagedSet<I>> getType() {
-            return type;
-        }
-
-        @Override
-        public ManagedSet<I> getInstance() {
-            return new ModelNodeBackedManagedSet();
-        }
-
-        @Override
-        public void close() {
-            closed = true;
-        }
-
-        private void ensureReadable() {
-            if (writable && !closed) {
-                throw new WriteOnlyModelViewException(getType(), ruleDescriptor);
-            }
-            if (elementViews == null) {
-                elementViews = new LinkedHashSet<I>();
-                for (MutableModelNode node : modelNode.getLinks(elementSchema.getType())) {
-                    elementViews.add(node.asReadOnly(elementSchema.getType(), ruleDescriptor).getInstance());
-                }
-            }
-        }
-
-
-        public class ModelNodeBackedManagedSet implements ManagedSet<I>, ManagedInstance {
-            @Override
-            public MutableModelNode getBackingNode() {
-                return modelNode;
-            }
-
-            @Override
-            public String toString() {
-                return String.format("%s '%s'", getType(), modelNode.getPath().toString());
-            }
-
-            @Override
-            public void create(final Action<? super I> action) {
-                assertMutable();
-
-                // Generate a synthetic path for the element
-                String name = String.valueOf(modelNode.getLinkCount(elementSchema.getType()));
-                ModelPath path = modelNode.getPath().child(name);
-
-                modelNode.addLink(modelCreatorFactory.creator(ruleDescriptor, path, elementSchema, action));
-            }
-
-            public void assertMutable() {
-                if (!writable || closed) {
-                    throw new ModelViewClosedException(getType(), ruleDescriptor);
-                }
-            }
-
-            @Override
-            public void afterEach(Action<? super I> configAction) {
-                assertMutable();
-                modelNode.applyToAllLinks(ModelActionRole.Finalize, new ActionBackedModelAction<I>(elementReference, NestedModelRuleDescriptor.append(ruleDescriptor, "afterEach()"), configAction));
-            }
-
-            @Override
-            public void beforeEach(Action<? super I> configAction) {
-                assertMutable();
-                modelNode.applyToAllLinks(ModelActionRole.Defaults, new ActionBackedModelAction<I>(elementReference, NestedModelRuleDescriptor.append(ruleDescriptor, "afterEach()"), configAction));
-            }
-
-            @Override
-            public int size() {
-                ensureReadable();
-                return elementViews.size();
-            }
-
-            @Override
-            public boolean isEmpty() {
-                ensureReadable();
-                return elementViews.isEmpty();
-            }
-
-            @Override
-            public boolean contains(Object o) {
-                ensureReadable();
-                return elementViews.contains(o);
-            }
-
-            @Override
-            public Iterator<I> iterator() {
-                ensureReadable();
-                return elementViews.iterator();
-            }
-
-            @Override
-            public Object[] toArray() {
-                ensureReadable();
-                return elementViews.toArray();
-            }
-
-            @Override
-            public <T> T[] toArray(T[] a) {
-                ensureReadable();
-                return elementViews.toArray(a);
-            }
-
-            @Override
-            public boolean add(I e) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public boolean remove(Object o) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public boolean containsAll(Collection<?> c) {
-                ensureReadable();
-                return elementViews.containsAll(c);
-            }
-
-            @Override
-            public boolean addAll(Collection<? extends I> c) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public boolean retainAll(Collection<?> c) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public boolean removeAll(Collection<?> c) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-            public void clear() {
-                throw new UnsupportedOperationException();
-            }
-
-            // TODO - mix this in using decoration. Also validate closure parameter types, if declared
-            public void create(Closure<?> closure) {
-                create(ClosureBackedAction.of(closure));
-            }
-
-            public void afterEach(Closure<?> closure) {
-                afterEach(ClosureBackedAction.of(closure));
-            }
-
-            public void beforeEach(Closure<?> closure) {
-                beforeEach(ClosureBackedAction.of(closure));
-            }
-
-        }
-    }
-
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelCollectionSchema.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelCollectionSchema.java
index a79a3a4..cab14f0 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelCollectionSchema.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelCollectionSchema.java
@@ -16,17 +16,32 @@
 
 package org.gradle.model.internal.manage.schema;
 
+import org.gradle.model.ModelMap;
+import org.gradle.model.ModelSet;
+import org.gradle.model.collection.ManagedSet;
 import org.gradle.model.internal.type.ModelType;
 
 public class ModelCollectionSchema<T> extends ModelSchema<T> {
     private final ModelType<?> elementType;
+    private boolean map;
 
     public ModelCollectionSchema(ModelType<T> type, ModelType<?> elementType) {
         super(type, Kind.COLLECTION);
         this.elementType = elementType;
+        if (type.getRawClass().equals(ModelMap.class)) {
+            map = true;
+        } else if (type.getRawClass().equals(ModelSet.class) || type.getRawClass().equals(ManagedSet.class)) {
+            map = false;
+        } else {
+            throw new IllegalArgumentException("Expected type of either ModelMap or ModelSet");
+        }
     }
 
     public ModelType<?> getElementType() {
         return elementType;
     }
+
+    public boolean isMap() {
+        return map;
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelMapSchema.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelMapSchema.java
new file mode 100644
index 0000000..0640ce7
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelMapSchema.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema;
+
+import org.gradle.model.internal.type.ModelType;
+
+import java.lang.ref.WeakReference;
+
+public class ModelMapSchema<T> extends ModelSchema<T> {
+    private final WeakReference<Class<?>> managedImpl;
+    private final ModelType<?> elementType;
+
+    public ModelMapSchema(ModelType<T> type, ModelType<?> elementType, Class<?> managedImpl) {
+        super(type, Kind.SPECIALIZED_MAP);
+        this.elementType = elementType;
+        this.managedImpl = new WeakReference<Class<?>>(managedImpl);
+    }
+
+    public ModelType<?> getElementType() {
+        return elementType;
+    }
+
+    public Class<?> getManagedImpl() {
+        return managedImpl.get();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchema.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchema.java
index d2c1997..da8a74e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchema.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchema.java
@@ -22,20 +22,21 @@ import org.gradle.model.internal.type.ModelType;
 @ThreadSafe
 public class ModelSchema<T> {
 
-    public static enum Kind {
+    public enum Kind {
         VALUE(false, true), // at the moment we are conflating this with unstructured primitives
         COLLECTION,
-        STRUCT, // type is guaranteed to be an interface
+        SPECIALIZED_MAP(false, false), // not quite
+        STRUCT,
         UNMANAGED(false, false); // some type we know nothing about
 
         private final boolean isManaged;
         private final boolean isAllowedPropertyTypeOfManagedType;
 
-        private Kind() {
+        Kind() {
             this(true, true);
         }
 
-        private Kind(boolean isManaged, boolean isAllowedPropertyTypeOfManagedType) {
+        Kind(boolean isManaged, boolean isAllowedPropertyTypeOfManagedType) {
             this.isManaged = isManaged;
             this.isAllowedPropertyTypeOfManagedType = isAllowedPropertyTypeOfManagedType;
         }
@@ -64,6 +65,10 @@ public class ModelSchema<T> {
         return new ModelCollectionSchema<T>(type, elementType);
     }
 
+    public static <T> ModelMapSchema<T> specializedMap(ModelType<T> type, ModelType<?> elementType, Class<?> managedImpl) {
+        return new ModelMapSchema<T>(type, elementType, managedImpl);
+    }
+
     public static <T> ModelSchema<T> unmanaged(ModelType<T> type) {
         return new ModelSchema<T>(type, Kind.UNMANAGED);
     }
@@ -81,4 +86,8 @@ public class ModelSchema<T> {
         return kind;
     }
 
+    @Override
+    public String toString() {
+        return kind.toString().toLowerCase() + " " + type;
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java
index 7dead48..1f75c8e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java
@@ -22,6 +22,8 @@ public interface ModelSchemaStore {
 
     <T> ModelSchema<T> getSchema(ModelType<T> type);
 
+    <T> ModelSchema<T> getSchema(Class<T> type);
+
     /**
      * Remove any cached information for types that have been GC'd.
      */
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/cache/ModelSchemaCache.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/cache/ModelSchemaCache.java
index 3265f37..4953077 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/cache/ModelSchemaCache.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/cache/ModelSchemaCache.java
@@ -38,7 +38,7 @@ import java.util.concurrent.locks.ReentrantLock;
  * and for strong class references not to be held for the cache keys.
  * <p>
  * The use of {@link WeakClassSet} as the cache key, opposed to just {@link Class}, is because a type may be composed of types from different classloaders.
- * An example of this would be something like {@code ManagedSet<SomeCustomUserType>}.
+ * An example of this would be something like {@code ModelSet<SomeCustomUserType>}.
  * The class set abstraction effectively creates a key for all classes involved in a type.
  * The {@link WeakClassSet#isCollected()} method returns true when any of the classes involved have been collected.
  * All keys (and associated entries) are removed from the map by the {@link #cleanUp()} method, which should be invoked periodically to trim the cache of no longer needed data.
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/AbstractProxyClassGenerator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/AbstractProxyClassGenerator.java
new file mode 100644
index 0000000..649ebfd
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/AbstractProxyClassGenerator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract;
+
+import org.gradle.internal.Cast;
+import org.gradle.internal.reflect.JavaMethod;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class AbstractProxyClassGenerator {
+    private static final JavaMethod<ClassLoader, ?> DEFINE_CLASS_METHOD = JavaReflectionUtil.method(ClassLoader.class, Class.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
+    protected static final String CONSTRUCTOR_NAME = "<init>";
+    protected static final String CONCRETE_SIGNATURE = null;
+    protected static final String[] NO_EXCEPTIONS = new String[0];
+
+    protected <T> Class<? extends T> defineClass(ClassWriter visitor, ClassLoader classLoader, String generatedTypeName) {
+        byte[] bytecode = visitor.toByteArray();
+        return Cast.uncheckedCast(DEFINE_CLASS_METHOD.invoke(classLoader, generatedTypeName, bytecode, 0, bytecode.length));
+    }
+
+    protected void putThisOnStack(MethodVisitor constructorVisitor) {
+        constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+    }
+
+    protected void finishVisitingMethod(MethodVisitor methodVisitor) {
+        finishVisitingMethod(methodVisitor, Opcodes.RETURN);
+    }
+
+    protected void finishVisitingMethod(MethodVisitor methodVisitor, int returnOpcode) {
+        methodVisitor.visitInsn(returnOpcode);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+    }
+
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java
index bd64709..f5fcd1e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java
@@ -42,6 +42,11 @@ public class DefaultModelSchemaStore implements ModelSchemaStore {
     }
 
     @Override
+    public <T> ModelSchema<T> getSchema(Class<T> type) {
+        return getSchema(ModelType.of(type));
+    }
+
+    @Override
     public void cleanUp() {
         cache.cleanUp();
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/InvalidManagedModelElementTypeException.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/InvalidManagedModelElementTypeException.java
index c753cdb..6df3845 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/InvalidManagedModelElementTypeException.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/InvalidManagedModelElementTypeException.java
@@ -17,12 +17,14 @@
 package org.gradle.model.internal.manage.schema.extract;
 
 import com.google.common.collect.Lists;
+import org.gradle.internal.exceptions.Contextual;
 import org.gradle.model.internal.type.ModelType;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Deque;
 
+ at Contextual
 public class InvalidManagedModelElementTypeException extends RuntimeException {
 
     private static String createPathString(ModelSchemaExtractionContext<?> extractionContext) {
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/JdkValueTypeStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/JdkValueTypeStrategy.java
index a7c9c30..40e1db3 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/JdkValueTypeStrategy.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/JdkValueTypeStrategy.java
@@ -24,6 +24,7 @@ import org.gradle.model.internal.manage.schema.ModelSchema;
 import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache;
 import org.gradle.model.internal.type.ModelType;
 
+import java.io.File;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collections;
@@ -39,7 +40,8 @@ public class JdkValueTypeStrategy implements ModelSchemaExtractionStrategy {
             ModelType.of(Long.class),
             ModelType.of(Double.class),
             ModelType.of(BigInteger.class),
-            ModelType.of(BigDecimal.class)
+            ModelType.of(BigDecimal.class),
+            ModelType.of(File.class)
     );
 
     // Expected to be a subset of above
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGenerator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGenerator.java
new file mode 100644
index 0000000..9d92095
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Constructor;
+
+public class ManagedCollectionProxyClassGenerator extends AbstractProxyClassGenerator {
+    /**
+     * Generates an implementation of the given managed type.
+     *
+     * <p>The generated type will:</p>
+     *
+     * <ul>
+     *     <li>extend the given implementation class</li>
+     *     <li>implement the given public interface</li>
+     *     <li>override each public constructor of the given implementation class</li>
+     * </ul>
+     */
+    public Class<?> generate(Class<?> implClass, Class<?> publicContractType) {
+        ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+
+        String generatedTypeName = publicContractType.getName() + "_Impl";
+        Type generatedType = Type.getType("L" + generatedTypeName.replaceAll("\\.", "/") + ";");
+
+        Type superclassType = Type.getType(implClass);
+        Type publicType = Type.getType(publicContractType);
+
+        generateClass(visitor, generatedType, superclassType, publicType);
+        generateConstructors(visitor, implClass, superclassType);
+        visitor.visitEnd();
+
+        return defineClass(visitor, publicContractType.getClassLoader(), generatedTypeName);
+    }
+
+    private <T> void generateConstructors(ClassWriter visitor, Class<? extends T> implClass, Type superclassType) {
+        for (Constructor<?> constructor : implClass.getConstructors()) {
+            Type[] paramTypes = new Type[constructor.getParameterTypes().length];
+            for (int i = 0; i < paramTypes.length; i++) {
+                paramTypes[i] = Type.getType(constructor.getParameterTypes()[i]);
+            }
+            String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, paramTypes);
+            MethodVisitor constructorVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, methodDescriptor, CONCRETE_SIGNATURE, NO_EXCEPTIONS);
+            constructorVisitor.visitCode();
+            putThisOnStack(constructorVisitor);
+            for (int i = 0; i < paramTypes.length; i++) {
+                constructorVisitor.visitVarInsn(Opcodes.ALOAD, i + 1);
+            }
+            constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), CONSTRUCTOR_NAME, methodDescriptor, false);
+            finishVisitingMethod(constructorVisitor);
+        }
+    }
+
+    private void generateClass(ClassWriter visitor, Type generatedType, Type superclassType, Type publicType) {
+        visitor.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null, superclassType.getInternalName(), new String[]{publicType.getInternalName()});
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedProxyClassGenerator.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedProxyClassGenerator.java
index f503e86..eef190c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedProxyClassGenerator.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedProxyClassGenerator.java
@@ -21,21 +21,19 @@ import com.google.common.collect.Iterables;
 import groovy.lang.MissingMethodException;
 import groovy.lang.MissingPropertyException;
 import org.apache.commons.lang.StringUtils;
-import org.gradle.internal.Cast;
-import org.gradle.internal.reflect.*;
+import org.gradle.internal.reflect.ClassDetails;
+import org.gradle.internal.reflect.ClassInspector;
+import org.gradle.internal.reflect.PropertyDetails;
 import org.gradle.model.internal.core.MutableModelNode;
 import org.gradle.model.internal.manage.instance.ManagedInstance;
 import org.gradle.model.internal.manage.instance.ModelElementState;
 import org.objectweb.asm.*;
 
-//CHECKSTYLE:OFF This import is needed to override wildcard import of of org.gradle.internal.reflect.NoSuchMethodException
-import java.lang.NoSuchMethodException;
-//CHECKSTYLE:ON
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.List;
 
-public class ManagedProxyClassGenerator {
+public class ManagedProxyClassGenerator extends AbstractProxyClassGenerator {
     /*
         Note: there is deliberately no internal synchronizing or caching at this level.
 
@@ -44,13 +42,8 @@ public class ManagedProxyClassGenerator {
         This allows us to avoid yet another weak class based cache, and importantly having to acquire a lock to instantiate an implementation.
      */
 
-    private static final JavaMethod<ClassLoader, ?> DEFINE_CLASS_METHOD = JavaReflectionUtil.method(ClassLoader.class, Class.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
-
     private static final String STATE_FIELD_NAME = "$state";
     private static final String CAN_CALL_SETTERS_FIELD_NAME = "$canCallSetters";
-    private static final String CONSTRUCTOR_NAME = "<init>";
-    private static final String CONCRETE_SIGNATURE = null;
-    private static final String[] NO_EXCEPTIONS = new String[0];
     private static final String STATE_SET_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class), Type.getType(Object.class));
     private static final String MANAGED_INSTANCE_TYPE = Type.getInternalName(ManagedInstance.class);
     private static final Type MODEL_ELEMENT_STATE_TYPE = Type.getType(ModelElementState.class);
@@ -100,11 +93,6 @@ public class ManagedProxyClassGenerator {
         return defineClass(visitor, managedTypeClass.getClassLoader(), generatedTypeName);
     }
 
-    private <T> Class<? extends T> defineClass(ClassWriter visitor, ClassLoader classLoader, String generatedTypeName) {
-        byte[] bytecode = visitor.toByteArray();
-        return Cast.uncheckedCast(DEFINE_CLASS_METHOD.invoke(classLoader, generatedTypeName, bytecode, 0, bytecode.length));
-    }
-
     private void generateProxyClass(ClassWriter visitor, Class<?> managedTypeClass, List<String> interfaceInternalNames, Type generatedType, Type superclassType) {
         declareClass(visitor, interfaceInternalNames, generatedType, superclassType);
         declareStateField(visitor);
@@ -135,7 +123,7 @@ public class ManagedProxyClassGenerator {
     }
 
     private void writeConstructor(ClassVisitor visitor, Type generatedType, Type superclassType) {
-        MethodVisitor constructorVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", CONSTRUCTOR_DESCRIPTOR, CONCRETE_SIGNATURE, NO_EXCEPTIONS);
+        MethodVisitor constructorVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESCRIPTOR, CONCRETE_SIGNATURE, NO_EXCEPTIONS);
         constructorVisitor.visitCode();
 
         invokeSuperConstructor(constructorVisitor, superclassType);
@@ -146,7 +134,7 @@ public class ManagedProxyClassGenerator {
 
     private void invokeSuperConstructor(MethodVisitor constructorVisitor, Type superclassType) {
         putThisOnStack(constructorVisitor);
-        constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false);
+        constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), CONSTRUCTOR_NAME, Type.getMethodDescriptor(Type.VOID_TYPE), false);
     }
 
     private void writeToString(ClassVisitor visitor, Type generatedType, Class<?> managedTypeClass) {
@@ -241,20 +229,6 @@ public class ManagedProxyClassGenerator {
         methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), CAN_CALL_SETTERS_FIELD_NAME, Type.BOOLEAN_TYPE.getDescriptor());
     }
 
-    private void putThisOnStack(MethodVisitor constructorVisitor) {
-        constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-    }
-
-    private void finishVisitingMethod(MethodVisitor methodVisitor) {
-        finishVisitingMethod(methodVisitor, Opcodes.RETURN);
-    }
-
-    private void finishVisitingMethod(MethodVisitor methodVisitor, int returnOpcode) {
-        methodVisitor.visitInsn(returnOpcode);
-        methodVisitor.visitMaxs(0, 0);
-        methodVisitor.visitEnd();
-    }
-
     private void writeMutationMethods(ClassVisitor visitor, Type generatedType, Class<?> managedTypeClass) {
         ClassDetails classDetails = ClassInspector.inspect(managedTypeClass);
         for (PropertyDetails property : classDetails.getProperties()) {
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedSetStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedSetStrategy.java
index 9c6f313..50c9897 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedSetStrategy.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ManagedSetStrategy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 the original author or authors.
+ * Copyright 2015 the original author 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,70 +16,23 @@
 
 package org.gradle.model.internal.manage.schema.extract;
 
-import com.google.common.collect.ImmutableList;
 import net.jcip.annotations.ThreadSafe;
-import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.model.collection.ManagedSet;
-import org.gradle.model.internal.manage.schema.ModelSchema;
-import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache;
 import org.gradle.model.internal.type.ModelType;
 
 import java.util.Collections;
-import java.util.List;
 
 @ThreadSafe
-public class ManagedSetStrategy implements ModelSchemaExtractionStrategy {
-
-    private static final ModelType<ManagedSet<?>> MANAGED_SET_MODEL_TYPE = new ModelType<ManagedSet<?>>() {
-    };
-    private final Factory<String> supportedTypeDescriptions;
+public class ManagedSetStrategy extends SetStrategy {
 
     public ManagedSetStrategy(Factory<String> supportedTypeDescriptions) {
-        this.supportedTypeDescriptions = supportedTypeDescriptions;
-    }
-
-    public <T> ModelSchemaExtractionResult<T> extract(ModelSchemaExtractionContext<T> extractionContext, final ModelSchemaCache cache) {
-        ModelType<T> type = extractionContext.getType();
-        if (MANAGED_SET_MODEL_TYPE.isAssignableFrom(type)) {
-            if (!type.getRawClass().equals(ManagedSet.class)) {
-                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("subtyping %s is not supported", ManagedSet.class.getName()));
-            }
-            if (type.isHasWildcardTypeVariables()) {
-                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s cannot be a wildcard", ManagedSet.class.getName()));
-            }
-
-            List<ModelType<?>> typeVariables = type.getTypeVariables();
-            if (typeVariables.isEmpty()) {
-                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s has to be specified", ManagedSet.class.getName()));
-            }
-
-            ModelType<?> elementType = typeVariables.get(0);
-
-            if (MANAGED_SET_MODEL_TYPE.isAssignableFrom(elementType)) {
-                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("%1$s cannot be used as type parameter of %1$s", ManagedSet.class.getName()));
-            }
-
-            ModelSchema<T> schema = ModelSchema.collection(extractionContext.getType(), elementType);
-            ModelSchemaExtractionContext<?> typeParamExtractionContext = extractionContext.child(elementType, "element type", new Action<ModelSchemaExtractionContext<?>>() {
-                public void execute(ModelSchemaExtractionContext<?> context) {
-                    ModelSchema<?> typeParamSchema = cache.get(context.getType());
-
-                    if (!typeParamSchema.getKind().isManaged()) {
-                        throw new InvalidManagedModelElementTypeException(context.getParent(), String.format(
-                                "cannot create a managed set of type %s as it is an unmanaged type.%nSupported types:%n%s",
-                                context.getType(), supportedTypeDescriptions.create()
-                        ));
-                    }
-                }
-            });
-            return new ModelSchemaExtractionResult<T>(schema, ImmutableList.of(typeParamExtractionContext));
-        } else {
-            return null;
-        }
+        super(new ModelType<ManagedSet<?>>() {
+        }, supportedTypeDescriptions);
     }
 
+    @Override
     public Iterable<String> getSupportedManagedTypes() {
-        return Collections.singleton(MANAGED_SET_MODEL_TYPE + " of a managed type");
+        return Collections.emptySet();
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelMapStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelMapStrategy.java
new file mode 100644
index 0000000..3982eec
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelMapStrategy.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract;
+
+import com.google.common.collect.ImmutableList;
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.Action;
+import org.gradle.model.Managed;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.manage.schema.ModelSchema;
+import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collections;
+import java.util.List;
+
+ at ThreadSafe
+public class ModelMapStrategy implements ModelSchemaExtractionStrategy {
+
+    private static final ModelType<ModelMap<?>> MODEL_MAP_MODEL_TYPE = new ModelType<ModelMap<?>>() {
+    };
+
+    // TODO extract common stuff from this and ModelSet and reuse
+
+    public <T> ModelSchemaExtractionResult<T> extract(ModelSchemaExtractionContext<T> extractionContext, final ModelSchemaCache cache) {
+        ModelType<T> type = extractionContext.getType();
+        if (MODEL_MAP_MODEL_TYPE.isAssignableFrom(type)) {
+            if (!type.getRawClass().equals(ModelMap.class)) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("subtyping %s is not supported.", ModelMap.class.getName()));
+            }
+            if (type.isHasWildcardTypeVariables()) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s cannot be a wildcard.", ModelMap.class.getName()));
+            }
+
+            List<ModelType<?>> typeVariables = type.getTypeVariables();
+            if (typeVariables.isEmpty()) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s has to be specified.", ModelMap.class.getName()));
+            }
+
+            ModelType<?> elementType = typeVariables.get(0);
+
+            if (MODEL_MAP_MODEL_TYPE.isAssignableFrom(elementType)) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("%1$s cannot be used as type parameter of %1$s.", ModelMap.class.getName()));
+            }
+
+            ModelSchema<T> schema = ModelSchema.collection(extractionContext.getType(), elementType);
+            ModelSchemaExtractionContext<?> typeParamExtractionContext = extractionContext.child(elementType, "element type", new Action<ModelSchemaExtractionContext<?>>() {
+                public void execute(ModelSchemaExtractionContext<?> context) {
+                    ModelType<?> elementType = context.getType();
+                    ModelSchema<?> typeParamSchema = cache.get(elementType);
+
+                    if (!typeParamSchema.getKind().isManaged()) {
+                        throw new InvalidManagedModelElementTypeException(context.getParent(), String.format(
+                            "cannot create a model map of type %s as it is not a %s type.",
+                            elementType, Managed.class.getName()
+                        ));
+                    }
+                }
+            });
+            return new ModelSchemaExtractionResult<T>(schema, ImmutableList.of(typeParamExtractionContext));
+        } else {
+            return null;
+        }
+    }
+
+    public Iterable<String> getSupportedManagedTypes() {
+        return Collections.singleton(MODEL_MAP_MODEL_TYPE + " of a managed type");
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractor.java
index 3dc7e31..f9f99b5 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractor.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractor.java
@@ -41,12 +41,15 @@ class ModelSchemaExtractor {
         }
     };
     private final List<ModelSchemaExtractionStrategy> strategies = ImmutableList.of(
-            new PrimitiveStrategy(),
-            new EnumStrategy(),
-            new JdkValueTypeStrategy(),
-            new ManagedSetStrategy(supportedTypeDescriptions),
-            new StructStrategy(supportedTypeDescriptions),
-            new UnmanagedStrategy()
+        new PrimitiveStrategy(),
+        new EnumStrategy(),
+        new JdkValueTypeStrategy(),
+        new ModelSetStrategy(supportedTypeDescriptions),
+        new ManagedSetStrategy(supportedTypeDescriptions),
+        new StructStrategy(supportedTypeDescriptions),
+        new SpecializedMapStrategy(),
+        new ModelMapStrategy(),
+        new UnmanagedStrategy()
     );
 
     public <T> ModelSchema<T> extract(ModelType<T> type, ModelSchemaCache cache) {
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSetStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSetStrategy.java
new file mode 100644
index 0000000..a2b5b72
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/ModelSetStrategy.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.model.internal.manage.schema.extract;
+
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.Factory;
+import org.gradle.model.ModelSet;
+import org.gradle.model.internal.type.ModelType;
+
+ at ThreadSafe
+public class ModelSetStrategy extends SetStrategy {
+
+    public ModelSetStrategy(Factory<String> supportedTypeDescriptions) {
+        super(new ModelType<ModelSet<?>>() {
+        }, supportedTypeDescriptions);
+    }
+
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SetStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SetStrategy.java
new file mode 100644
index 0000000..288417d
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SetStrategy.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.Action;
+import org.gradle.internal.Factory;
+import org.gradle.model.internal.manage.schema.ModelSchema;
+import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache;
+import org.gradle.model.internal.type.ModelType;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class SetStrategy implements ModelSchemaExtractionStrategy {
+
+    private final ModelType<?> modelType;
+    protected final Factory<String> supportedTypeDescriptions;
+
+    public SetStrategy(ModelType<?> modelType, Factory<String> supportedTypeDescriptions) {
+        this.modelType = modelType;
+        this.supportedTypeDescriptions = supportedTypeDescriptions;
+    }
+
+    public <T> ModelSchemaExtractionResult<T> extract(ModelSchemaExtractionContext<T> extractionContext, final ModelSchemaCache cache) {
+        ModelType<T> type = extractionContext.getType();
+        if (modelType.isAssignableFrom(type)) {
+            if (!type.getRawClass().equals(modelType.getConcreteClass())) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("subtyping %s is not supported", modelType.getConcreteClass().getName()));
+            }
+            if (type.isHasWildcardTypeVariables()) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s cannot be a wildcard", modelType.getConcreteClass().getName()));
+            }
+
+            List<ModelType<?>> typeVariables = type.getTypeVariables();
+            if (typeVariables.isEmpty()) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("type parameter of %s has to be specified", modelType.getConcreteClass().getName()));
+            }
+
+            ModelType<?> elementType = typeVariables.get(0);
+
+            if (modelType.isAssignableFrom(elementType)) {
+                throw new InvalidManagedModelElementTypeException(extractionContext, String.format("%1$s cannot be used as type parameter of %1$s", modelType.getConcreteClass().getName()));
+            }
+
+            ModelSchema<T> schema = ModelSchema.collection(extractionContext.getType(), elementType);
+            ModelSchemaExtractionContext<?> typeParamExtractionContext = extractionContext.child(elementType, "element type", new Action<ModelSchemaExtractionContext<?>>() {
+                public void execute(ModelSchemaExtractionContext<?> context) {
+                    ModelSchema<?> typeParamSchema = cache.get(context.getType());
+
+                    if (!typeParamSchema.getKind().isManaged()) {
+                        throw new InvalidManagedModelElementTypeException(context.getParent(), String.format(
+                            "cannot create a managed set of type %s as it is an unmanaged type.%nSupported types:%n%s",
+                            context.getType(), supportedTypeDescriptions.create()
+                        ));
+                    }
+                }
+            });
+            return new ModelSchemaExtractionResult<T>(schema, ImmutableList.of(typeParamExtractionContext));
+        } else {
+            return null;
+        }
+    }
+
+    public Iterable<String> getSupportedManagedTypes() {
+        return Collections.singleton(modelType + " of a managed type");
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SpecializedMapStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SpecializedMapStrategy.java
new file mode 100644
index 0000000..83e7fce
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/SpecializedMapStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract;
+
+import org.gradle.api.Nullable;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.ModelMapGroovyDecorator;
+import org.gradle.model.internal.manage.schema.ModelSchema;
+import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache;
+import org.gradle.model.internal.type.ModelType;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collections;
+
+/**
+ * Currently only handles interfaces with no type parameters that directly extend ModelMap.
+ */
+public class SpecializedMapStrategy implements ModelSchemaExtractionStrategy {
+    private final ManagedCollectionProxyClassGenerator generator = new ManagedCollectionProxyClassGenerator();
+
+    @Nullable
+    @Override
+    public <T> ModelSchemaExtractionResult<T> extract(ModelSchemaExtractionContext<T> extractionContext, ModelSchemaCache cache) {
+        Type type = extractionContext.getType().getType();
+        if (!(type instanceof Class)) {
+            return null;
+        }
+        Class<?> contractType = (Class<?>) type;
+        if (!contractType.isInterface()) {
+            return null;
+        }
+        if (contractType.getGenericInterfaces().length != 1) {
+            return null;
+        }
+        Type superType = contractType.getGenericInterfaces()[0];
+        if (!(superType instanceof ParameterizedType)) {
+            return null;
+        }
+        ParameterizedType parameterizedSuperType = (ParameterizedType) superType;
+        if (!parameterizedSuperType.getRawType().equals(ModelMap.class)) {
+            return null;
+        }
+        ModelType<?> elementType = ModelType.of(parameterizedSuperType.getActualTypeArguments()[0]);
+        Class<?> proxyImpl = generator.generate(ModelMapGroovyDecorator.class, contractType);
+        return new ModelSchemaExtractionResult<T>(ModelSchema.specializedMap(extractionContext.getType(), elementType, proxyImpl));
+    }
+
+    @Override
+    public Iterable<String> getSupportedManagedTypes() {
+        return Collections.emptySet();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/StructStrategy.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/StructStrategy.java
index 415bee2..4cf80c8 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/StructStrategy.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/StructStrategy.java
@@ -21,15 +21,18 @@ import com.google.common.collect.*;
 import groovy.lang.GroovyObject;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.Action;
+import org.gradle.api.Named;
 import org.gradle.api.Nullable;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.MethodDescription;
 import org.gradle.internal.reflect.MethodSignatureEquivalence;
 import org.gradle.model.Managed;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Unmanaged;
 import org.gradle.model.internal.core.MutableModelNode;
 import org.gradle.model.internal.manage.instance.ManagedProxyFactory;
 import org.gradle.model.internal.manage.instance.ModelElementState;
+import org.gradle.model.internal.manage.schema.ModelCollectionSchema;
 import org.gradle.model.internal.manage.schema.ModelProperty;
 import org.gradle.model.internal.manage.schema.ModelSchema;
 import org.gradle.model.internal.manage.schema.ModelStructSchema;
@@ -211,32 +214,54 @@ public class StructStrategy implements ModelSchemaExtractionStrategy {
             public void execute(ModelSchemaExtractionContext<P> propertyExtractionContext) {
                 ModelSchema<P> propertySchema = modelSchemaCache.get(property.getType());
 
+                if (property.getName().equals("name") && Named.class.isAssignableFrom(parentContext.getType().getRawClass())) {
+                    if (property.isWritable()) {
+                        throw new InvalidManagedModelElementTypeException(parentContext, String.format(
+                            "@Managed types implementing %s must not declare a setter for the name property",
+                            Named.class.getName()
+                        ));
+                    } else {
+                        return;
+                    }
+                }
+
                 if (propertySchema.getKind().isAllowedPropertyTypeOfManagedType() && property.isUnmanaged()) {
                     throw new InvalidManagedModelElementTypeException(parentContext, String.format(
-                            "property '%s' is marked as @Unmanaged, but is of @Managed type '%s'. Please remove the @Managed annotation.%n%s",
-                            property.getName(), property.getType(), supportedTypeDescriptions.create()
+                        "property '%s' is marked as @Unmanaged, but is of @Managed type '%s'. Please remove the @Managed annotation.%n%s",
+                        property.getName(), property.getType(), supportedTypeDescriptions.create()
                     ));
                 }
 
                 if (!propertySchema.getKind().isAllowedPropertyTypeOfManagedType() && !property.isUnmanaged()) {
                     throw new InvalidManagedModelElementTypeException(parentContext, String.format(
-                            "type %s cannot be used for property '%s' as it is an unmanaged type (please annotate the getter with @org.gradle.model.Unmanaged if you want this property to be unmanaged).%n%s",
-                            property.getType(), property.getName(), supportedTypeDescriptions.create()
+                        "type %s cannot be used for property '%s' as it is an unmanaged type (please annotate the getter with @org.gradle.model.Unmanaged if you want this property to be unmanaged).%n%s",
+                        property.getType(), property.getName(), supportedTypeDescriptions.create()
                     ));
                 }
 
                 if (!property.isWritable()) {
                     if (property.isUnmanaged()) {
                         throw new InvalidManagedModelElementTypeException(parentContext, String.format(
-                                "unmanaged property '%s' cannot be read only, unmanaged properties must have setters",
-                                property.getName())
+                            "unmanaged property '%s' cannot be read only, unmanaged properties must have setters",
+                            property.getName())
                         );
                     }
 
                     if (!propertySchema.getKind().isManaged()) {
                         throw new InvalidManagedModelElementTypeException(parentContext, String.format(
-                                "read only property '%s' has non managed type %s, only managed types can be used",
-                                property.getName(), property.getType()));
+                            "read only property '%s' has non managed type %s, only managed types can be used",
+                            property.getName(), property.getType()));
+                    }
+                }
+
+                if (propertySchema.getKind() == ModelSchema.Kind.COLLECTION) {
+                    ModelCollectionSchema<?> propertyCollectionSchema = (ModelCollectionSchema<?>) propertySchema;
+                    if (propertyCollectionSchema.isMap()) {
+                        if (property.isWritable()) {
+                            throw new InvalidManagedModelElementTypeException(parentContext, String.format(
+                                "property '%s' cannot have a setter (%s properties must be read only).",
+                                property.getName(), ModelMap.class.getName()));
+                        }
                     }
                 }
             }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/AnyStateBindingPredicate.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/AnyStateBindingPredicate.java
new file mode 100644
index 0000000..79e656e
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/AnyStateBindingPredicate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+import org.gradle.api.Nullable;
+import org.gradle.model.internal.core.ModelNode;
+import org.gradle.model.internal.core.ModelReference;
+
+public class AnyStateBindingPredicate extends BindingPredicate {
+    public AnyStateBindingPredicate(ModelReference<?> reference) {
+        super(reference);
+    }
+
+    @Nullable
+    @Override
+    public ModelNode.State getState() {
+        return null;
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BinderCreationListener.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BinderCreationListener.java
deleted file mode 100644
index 84351f5..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BinderCreationListener.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.model.internal.registry;
-
-import org.gradle.model.internal.core.ModelPromise;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-
-abstract class BinderCreationListener extends ModelCreationListener {
-    final ModelRuleDescriptor descriptor;
-    final ModelReference<?> reference;
-    final boolean writable;
-
-    public BinderCreationListener(ModelRuleDescriptor descriptor, ModelReference<?> reference, boolean writable) {
-        this.descriptor = descriptor;
-        this.reference = reference;
-        this.writable = writable;
-    }
-
-    boolean isTypeCompatible(ModelPromise promise) {
-        return writable ? promise.canBeViewedAsWritable(reference.getType()) : promise.canBeViewedAsReadOnly(reference.getType());
-    }
-
-    @Override
-    public String toString() {
-        return "ModelCreationListener{parent=" + matchParent() + ", path=" + matchPath() + ", scope=" + matchScope() + ", type=" + matchType() + "class=" + getClass().getSimpleName() + '}';
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BindingPredicate.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BindingPredicate.java
new file mode 100644
index 0000000..5526aca
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/BindingPredicate.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+import org.gradle.api.Nullable;
+import org.gradle.model.internal.core.ModelNode;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.core.ModelPredicate;
+import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.type.ModelType;
+
+public class BindingPredicate extends ModelPredicate {
+    private final ModelReference<?> reference;
+
+    public BindingPredicate(ModelReference<?> reference) {
+        this.reference = reference;
+    }
+
+    public ModelReference<?> getReference() {
+        return reference;
+    }
+
+    @Override
+    public String toString() {
+        return "{type: " + getType() + ", path: " + getPath() + ", scope: " + getScope() + ", state: " + getState() + "}";
+    }
+
+    @Nullable
+    @Override
+    public ModelPath getPath() {
+        return reference.getPath();
+    }
+
+    @Nullable
+    @Override
+    public ModelType<?> getType() {
+        return reference.getType();
+    }
+
+    @Nullable
+    public ModelPath getScope() {
+        return reference.getScope();
+    }
+
+    @Nullable
+    public ModelNode.State getState() {
+        return reference.getState();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/CreatorRuleBinder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/CreatorRuleBinder.java
index 4645968..fa56872 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/CreatorRuleBinder.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/CreatorRuleBinder.java
@@ -17,15 +17,15 @@
 package org.gradle.model.internal.registry;
 
 import org.gradle.model.internal.core.ModelCreator;
-import org.gradle.model.internal.core.ModelPath;
 
 import java.util.Collection;
+import java.util.List;
 
-public class CreatorRuleBinder extends RuleBinder {
+class CreatorRuleBinder extends RuleBinder {
     private final ModelCreator creator;
 
-    public CreatorRuleBinder(ModelCreator creator, ModelPath scope, Collection<RuleBinder> binders) {
-        super(creator.getInputs(), creator.getDescriptor(), scope, binders);
+    public CreatorRuleBinder(ModelCreator creator, BindingPredicate subject, List<BindingPredicate> inputs, Collection<RuleBinder> binders) {
+        super(subject, inputs, creator.getDescriptor(), binders);
         this.creator = creator;
     }
 
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java
index 849a1b3..9c2f39e 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java
@@ -23,7 +23,6 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import net.jcip.annotations.NotThreadSafe;
-import org.gradle.api.Action;
 import org.gradle.api.Nullable;
 import org.gradle.internal.BiActions;
 import org.gradle.internal.Cast;
@@ -38,11 +37,8 @@ import org.gradle.model.internal.type.ModelType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.*;
 
-import static org.gradle.model.internal.core.ModelActionRole.*;
 import static org.gradle.model.internal.core.ModelNode.State.*;
 
 @NotThreadSafe
@@ -51,20 +47,18 @@ public class DefaultModelRegistry implements ModelRegistry {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModelRegistry.class);
 
     private final ModelGraph modelGraph;
+    private final RuleBindings ruleBindings;
     private final ModelRuleExtractor ruleExtractor;
-
-    private final Set<RuleBinder> binders = Sets.newIdentityHashSet();
-    private final List<MutatorRuleBinder<?>> pendingMutatorBinders = Lists.newLinkedList();
-    private final List<MutatorRuleBinder<?>> unboundSubjectMutatorBinders = Lists.newLinkedList();
-    private final LinkedHashMap<ModelRule, ModelBinding<?>> rulesWithInputsBeingClosed = Maps.newLinkedHashMap();
+    private final Set<RuleBinder> unboundRules = Sets.newIdentityHashSet();
 
     boolean reset;
 
     public DefaultModelRegistry(ModelRuleExtractor ruleExtractor) {
         this.ruleExtractor = ruleExtractor;
-        ModelCreator rootCreator = ModelCreators.of(ModelReference.of(ModelPath.ROOT), BiActions.doNothing()).descriptor("<root>").withProjection(EmptyModelProjection.INSTANCE).build();
-        modelGraph = new ModelGraph(new ModelElementNode(toCreatorBinder(rootCreator)));
+        ModelCreator rootCreator = ModelCreators.of(ModelPath.ROOT, BiActions.doNothing()).descriptor("<root>").withProjection(EmptyModelProjection.INSTANCE).build();
+        modelGraph = new ModelGraph(new ModelElementNode(toCreatorBinder(rootCreator, ModelPath.ROOT), null));
         modelGraph.getRoot().setState(Created);
+        ruleBindings = new RuleBindings(modelGraph);
     }
 
     private static String toString(ModelRuleDescriptor descriptor) {
@@ -79,16 +73,20 @@ public class DefaultModelRegistry implements ModelRegistry {
             throw new InvalidModelRuleDeclarationException(creator.getDescriptor(), "Cannot create element at '" + path + "', only top level is allowed (e.g. '" + path.getRootParent() + "')");
         }
 
-        registerNode(modelGraph.getRoot(), new ModelElementNode(toCreatorBinder(creator)));
+        ModelNodeInternal root = modelGraph.getRoot();
+        registerNode(root, new ModelElementNode(toCreatorBinder(creator, ModelPath.ROOT), root));
         return this;
     }
 
-    public CreatorRuleBinder toCreatorBinder(ModelCreator creator) {
-        return new CreatorRuleBinder(creator, ModelPath.ROOT, binders);
+    private CreatorRuleBinder toCreatorBinder(ModelCreator creator, ModelPath scope) {
+        BindingPredicate subject = new BindingPredicate(ModelReference.of(creator.getPath(), ModelType.untyped(), ModelNode.State.Created));
+        List<BindingPredicate> inputs = mapInputs(creator.getInputs(), scope);
+        return new CreatorRuleBinder(creator, subject, inputs, unboundRules);
     }
 
     private ModelNodeInternal registerNode(ModelNodeInternal parent, ModelNodeInternal child) {
         if (reset) {
+            unboundRules.remove(child.getCreatorBinder());
             return child;
         }
 
@@ -102,36 +100,38 @@ public class DefaultModelRegistry implements ModelRegistry {
         if (node != null) {
             if (node.getState() == Known) {
                 throw new DuplicateModelException(
-                        String.format(
-                                "Cannot create '%s' using creation rule '%s' as the rule '%s' is already registered to create this model element.",
-                                path,
-                                toString(creator.getDescriptor()),
-                                toString(node.getDescriptor())
-                        )
+                    String.format(
+                        "Cannot create '%s' using creation rule '%s' as the rule '%s' is already registered to create this model element.",
+                        path,
+                        toString(creator.getDescriptor()),
+                        toString(node.getDescriptor())
+                    )
                 );
             }
             throw new DuplicateModelException(
-                    String.format(
-                            "Cannot create '%s' using creation rule '%s' as the rule '%s' has already been used to create this model element.",
-                            path,
-                            toString(creator.getDescriptor()),
-                            toString(node.getDescriptor())
-                    )
+                String.format(
+                    "Cannot create '%s' using creation rule '%s' as the rule '%s' has already been used to create this model element.",
+                    path,
+                    toString(creator.getDescriptor()),
+                    toString(node.getDescriptor())
+                )
             );
         }
         if (!parent.isMutable()) {
             throw new IllegalStateException(
-                    String.format(
-                            "Cannot create '%s' using creation rule '%s' as model element '%s' is no longer mutable.",
-                            path,
-                            toString(creator.getDescriptor()),
-                            parent.getPath()
-                    )
+                String.format(
+                    "Cannot create '%s' using creation rule '%s' as model element '%s' is no longer mutable.",
+                    path,
+                    toString(creator.getDescriptor()),
+                    parent.getPath()
+                )
             );
         }
 
         node = parent.addLink(child);
+        ruleBindings.add(node);
         modelGraph.add(node);
+        ruleBindings.add(node.getCreatorBinder());
         return node;
     }
 
@@ -155,64 +155,10 @@ public class DefaultModelRegistry implements ModelRegistry {
         if (reset) {
             return;
         }
-
-        MutatorRuleBinder<T> binder = new MutatorRuleBinder<T>(subject, role, mutator, scope, binders);
-
-        pendingMutatorBinders.add(binder);
-    }
-
-    private void flushPendingMutatorBinders() {
-        Iterator<MutatorRuleBinder<?>> iterator = pendingMutatorBinders.iterator();
-        while (iterator.hasNext()) {
-            MutatorRuleBinder<?> binder = iterator.next();
-            iterator.remove();
-            bindMutatorSubject(binder);
-        }
-    }
-
-    private <T> void bindMutatorSubject(final MutatorRuleBinder<T> binder) {
-        ModelCreationListener listener = listener(binder.getDescriptor(), binder.getSubjectReference(), binder.getScope(), true, new Action<ModelNodeInternal>() {
-            public void execute(ModelNodeInternal subject) {
-                if (!subject.canApply(binder.getRole())) {
-                    throw new IllegalStateException(String.format(
-                            "Cannot add %s rule '%s' for model element '%s' when element is in state %s.",
-                            binder.getRole(),
-                            binder.getAction().getDescriptor(),
-                            subject.getPath(),
-                            subject.getState()
-                    ));
-                }
-                unboundSubjectMutatorBinders.remove(binder);
-                binder.bindSubject(subject);
-                subject.addMutatorBinder(binder.getRole(), binder);
-            }
-        });
-        registerListener(listener);
-    }
-
-    private void bindInputs(final RuleBinder binder) {
-        if (!binder.isBindingInputs()) {
-            binder.setBindingInputs(true);
-            List<? extends ModelReference<?>> inputReferences = binder.getInputReferences();
-            for (int i = 0; i < inputReferences.size(); i++) {
-                final int finalI = i;
-                ModelReference<?> input = inputReferences.get(i);
-                ModelPath effectiveScope = input.getPath() == null ? ModelPath.ROOT : binder.getScope();
-                registerListener(listener(binder.getDescriptor(), input, effectiveScope, false, new Action<ModelNodeInternal>() {
-                    public void execute(ModelNodeInternal modelNode) {
-                        binder.bindInput(finalI, modelNode);
-                    }
-                }));
-                tryForceBind(binder);
-            }
-        }
-    }
-
-    private ModelCreationListener listener(ModelRuleDescriptor descriptor, ModelReference<?> reference, ModelPath scope, boolean writable, Action<? super ModelNodeInternal> bindAction) {
-        if (reference.getPath() != null) {
-            return new PathBinderCreationListener(descriptor, reference, scope, writable, bindAction);
-        }
-        return new OneOfTypeBinderCreationListener(descriptor, reference, scope, writable, bindAction);
+        BindingPredicate mappedSubject = mapSubject(subject, role, scope);
+        List<BindingPredicate> mappedInputs = mapInputs(mutator.getInputs(), scope);
+        MutatorRuleBinder<T> binder = new MutatorRuleBinder<T>(mappedSubject, mappedInputs, mutator, unboundRules);
+        ruleBindings.add(binder);
     }
 
     public <T> T realize(ModelPath path, ModelType<T> type) {
@@ -268,6 +214,8 @@ public class DefaultModelRegistry implements ModelRegistry {
         Iterable<? extends ModelNode> dependents = node.getDependents();
         if (Iterables.isEmpty(dependents)) {
             modelGraph.remove(node);
+            ruleBindings.remove(node);
+            unboundRules.remove(node.getCreatorBinder());
         } else {
             throw new RuntimeException("Tried to remove model " + path + " but it is depended on by: " + Joiner.on(", ").join(dependents));
         }
@@ -299,49 +247,40 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         // Will internally verify that this is valid
-        node.replaceCreatorRuleBinder(toCreatorBinder(newCreator));
+        node.replaceCreatorRuleBinder(toCreatorBinder(newCreator, ModelPath.ROOT));
+        ruleBindings.add(node.getCreatorBinder());
         return this;
     }
 
-    private ModelNode selfCloseAncestryAndSelf(ModelPath path) {
-        ModelPath parent = path.getParent();
-        if (parent != null) {
-            if (selfCloseAncestryAndSelf(parent) == null) {
-                return null;
-            }
-        }
-        return atStateOrLater(path, SelfClosed);
-    }
-
     public void bindAllReferences() throws UnboundModelRulesException {
-        flushPendingMutatorBinders();
-        if (binders.isEmpty()) {
+        if (unboundRules.isEmpty()) {
             return;
         }
 
+        GoalGraph graph = new GoalGraph();
         boolean newInputsBound = true;
-        while (!binders.isEmpty() && newInputsBound) {
+        while (!unboundRules.isEmpty() && newInputsBound) {
             newInputsBound = false;
-            RuleBinder[] unboundBinders = binders.toArray(new RuleBinder[binders.size()]);
+            RuleBinder[] unboundBinders = unboundRules.toArray(new RuleBinder[unboundRules.size()]);
             for (RuleBinder binder : unboundBinders) {
-                tryForceBind(binder);
+                transitionTo(graph, new TryBindInputs(binder));
                 newInputsBound = newInputsBound || binder.isBound();
             }
         }
 
-        if (!binders.isEmpty()) {
+        if (!unboundRules.isEmpty()) {
             SortedSet<RuleBinder> sortedBinders = new TreeSet<RuleBinder>(new Comparator<RuleBinder>() {
                 @Override
                 public int compare(RuleBinder o1, RuleBinder o2) {
                     return o1.getDescriptor().toString().compareTo(o2.getDescriptor().toString());
                 }
             });
-            sortedBinders.addAll(binders);
+            sortedBinders.addAll(unboundRules);
             throw unbound(sortedBinders);
         }
     }
 
-    private UnboundModelRulesException unbound(Iterable<RuleBinder> binders) {
+    private UnboundModelRulesException unbound(Iterable<? extends RuleBinder> binders) {
         ModelPathSuggestionProvider suggestionsProvider = new ModelPathSuggestionProvider(modelGraph.getFlattened().keySet());
         List<? extends UnboundRule> unboundRules = new UnboundRulesProcessor(binders, suggestionsProvider).process();
         return new UnboundModelRulesException(unboundRules);
@@ -374,130 +313,110 @@ public class DefaultModelRegistry implements ModelRegistry {
         transition(node, GraphClosed, false);
     }
 
-    private void transition(ModelNodeInternal node, ModelNode.State desired, boolean laterOk) {
-        ModelPath path = node.getPath();
-        ModelNode.State state = node.getState();
-
-        LOGGER.debug("Transitioning model element '{}' from state {} to {}", path, state.name(), desired.name());
-
-        if (desired.ordinal() < state.ordinal()) {
-            if (laterOk) {
-                return;
-            } else {
-                throw new IllegalStateException("Cannot lifecycle model node '" + path + "' to state " + desired.name() + " as it is already at " + state.name());
+    /**
+     * Attempts to achieve the given goal.
+     */
+    // TODO - reuse graph, discard state once not required
+    private void transitionTo(GoalGraph goalGraph, ModelGoal targetGoal) {
+        LinkedList<ModelGoal> queue = new LinkedList<ModelGoal>();
+        queue.add(targetGoal);
+        while (!queue.isEmpty()) {
+            ModelGoal goal = queue.getFirst();
+
+            if (goal.state == ModelGoal.State.Achieved) {
+                // Already reached this goal
+                queue.removeFirst();
+                continue;
             }
-        }
-
-        if (state == desired) {
-            return;
-        }
-
-        if (state == Known && desired.ordinal() >= Created.ordinal()) {
-            CreatorRuleBinder creatorBinder = node.getCreatorBinder();
-            if (creatorBinder != null) {
-                forceBind(creatorBinder);
+            if (goal.state == ModelGoal.State.NotSeen) {
+                if (goal.isAchieved()) {
+                    // Goal has previously been achieved or is no longer required
+                    goal.state = ModelGoal.State.Achieved;
+                    queue.removeFirst();
+                    continue;
+                }
             }
-            doCreate(node, creatorBinder);
-            node.notifyFired(creatorBinder);
-            node.setState(Created);
-
-            if (desired == Created) {
-                return;
+            if (goal.state == ModelGoal.State.VisitingDependencies) {
+                // All dependencies visited
+                goal.apply();
+                goal.state = ModelGoal.State.Achieved;
+                queue.removeFirst();
+                continue;
             }
-        }
-
-        if (!fireMutations(node, path, state, Defaults, DefaultsApplied, desired)) {
-            return;
-        }
-        if (!fireMutations(node, path, state, Initialize, Initialized, desired)) {
-            return;
-        }
-        if (!fireMutations(node, path, state, Mutate, Mutated, desired)) {
-            return;
-        }
-        if (!fireMutations(node, path, state, Finalize, Finalized, desired)) {
-            return;
-        }
-        if (!fireMutations(node, path, state, Validate, SelfClosed, desired)) {
-            return;
-        }
 
-        if (desired.ordinal() >= GraphClosed.ordinal()) {
-            for (ModelNodeInternal child : node.getLinks()) {
-                close(child);
+            // Add dependencies for this goal
+            List<ModelGoal> newDependencies = new ArrayList<ModelGoal>();
+            goal.attachNode();
+            boolean done = goal.calculateDependencies(goalGraph, newDependencies);
+            goal.state = done || newDependencies.isEmpty() ? ModelGoal.State.VisitingDependencies : ModelGoal.State.DiscoveringDependencies;
+
+            // Add dependencies to the start of the queue
+            for (int i = newDependencies.size() - 1; i >= 0; i--) {
+                ModelGoal dependency = newDependencies.get(i);
+                if (dependency.state == ModelGoal.State.Achieved) {
+                    continue;
+                }
+                if (dependency.state == ModelGoal.State.NotSeen) {
+                    queue.addFirst(dependency);
+                    continue;
+                }
+                throw ruleCycle(dependency, queue);
             }
-            node.setState(GraphClosed);
         }
-
-        LOGGER.debug("Finished transitioning model element {} from state {} to {}", path, state.name(), desired.name());
     }
 
-    private boolean forceBindReference(ModelReference<?> reference, ModelBinding<?> binding, ModelPath scope) {
-        if (binding == null) {
-            if (reference.getPath() == null) {
-                selfCloseAncestryAndSelf(scope);
+    private ConfigurationCycleException ruleCycle(ModelGoal brokenGoal, LinkedList<ModelGoal> queue) {
+        List<String> path = new ArrayList<String>();
+        int pos = queue.indexOf(brokenGoal);
+        ListIterator<ModelGoal> iterator = queue.listIterator(pos + 1);
+        while (iterator.hasPrevious()) {
+            ModelGoal goal = iterator.previous();
+            goal.attachToCycle(path);
+        }
+        brokenGoal.attachToCycle(path);
+
+        Formatter out = new Formatter();
+        out.format("A cycle has been detected in model rule dependencies. References forming the cycle:");
+        String last = null;
+        StringBuilder indent = new StringBuilder("");
+        for (int i = 0; i < path.size(); i++) {
+            String node = path.get(i);
+            // Remove duplicates
+            if (node.equals(last)) {
+                continue;
+            }
+            last = node;
+            if (i == 0) {
+                out.format("%n%s%s", indent, node);
             } else {
-                selfCloseAncestryAndSelf(reference.getPath().getParent());
+                out.format("%n%s\\- %s", indent, node);
+                indent.append("   ");
             }
-            return true;
         }
-        return false;
-    }
 
-    private boolean tryForceBind(RuleBinder binder) {
-        if (binder.isBound()) {
-            return false;
-        }
-        bindInputs(binder);
+        return new ConfigurationCycleException(out.toString());
+    }
 
-        boolean boundSomething = false;
-        ModelPath scope = binder.getScope();
+    private void transition(ModelNodeInternal node, ModelNode.State desired, boolean laterOk) {
+        ModelPath path = node.getPath();
+        ModelNode.State state = node.getState();
 
-        if (binder.getSubjectReference() != null && binder.getSubjectBinding() == null) {
-            if (forceBindReference(binder.getSubjectReference(), binder.getSubjectBinding(), scope)) {
-                boundSomething = binder.getSubjectBinding() != null;
-            }
-        }
+        LOGGER.debug("Transitioning model element '{}' from state {} to {}", path, state.name(), desired.name());
 
-        for (int i = 0; i < binder.getInputReferences().size(); i++) {
-            if (forceBindReference(binder.getInputReferences().get(i), binder.getInputBindings().get(i), scope)) {
-                boundSomething = boundSomething || binder.getInputBindings().get(i) != null;
+        if (desired.ordinal() < state.ordinal()) {
+            if (laterOk) {
+                return;
+            } else {
+                throw new IllegalStateException("Cannot lifecycle model node '" + path + "' to state " + desired.name() + " as it is already at " + state.name());
             }
         }
 
-        return boundSomething;
-    }
-
-    private void forceBind(RuleBinder binder) {
-        tryForceBind(binder);
-        if (!binder.isBound()) {
-            throw unbound(Collections.singleton(binder));
-        }
-    }
-
-    // NOTE: this should only be called from transition() as implicit logic is shared
-    private boolean fireMutations(ModelNodeInternal node, ModelPath path, ModelNode.State originalState, ModelActionRole type, ModelNode.State to, ModelNode.State desired) {
-        ModelNode.State nodeState = node.getState();
-        if (nodeState.ordinal() >= to.ordinal()) {
-            return nodeState.ordinal() < desired.ordinal();
-        }
-
-        flushPendingMutatorBinders();
-        for (MutatorRuleBinder<?> binder : node.getMutatorBinders(type)) {
-            forceBind(binder);
-            fireMutation(binder);
-            flushPendingMutatorBinders();
-            node.notifyFired(binder);
+        if (state == desired) {
+            return;
         }
 
-        node.setState(to);
-
-        if (to == desired) {
-            LOGGER.debug("Finished transitioning model element {} from state {} to {}", path, originalState.name(), desired.name());
-            return false;
-        } else {
-            return true;
-        }
+        GoalGraph goalGraph = new GoalGraph();
+        transitionTo(goalGraph, goalGraph.nodeAtState(new NodeAtState(node.getPath(), desired)));
     }
 
     private <T> ModelView<? extends T> assertView(ModelNodeInternal node, ModelType<T> targetType, @Nullable ModelRuleDescriptor descriptor, String msg, Object... msgArgs) {
@@ -522,14 +441,19 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
     }
 
-    private ModelNodeInternal doCreate(ModelNodeInternal node, CreatorRuleBinder boundCreator) {
-        ModelCreator creator = boundCreator.getCreator();
-        List<ModelView<?>> views = toViews(boundCreator.getInputBindings(), boundCreator.getCreator());
+    private ModelNodeInternal doCreate(final ModelNodeInternal node, CreatorRuleBinder boundCreator) {
+        final ModelCreator creator = boundCreator.getCreator();
+        final List<ModelView<?>> views = toViews(boundCreator.getInputBindings(), boundCreator.getCreator());
 
         LOGGER.debug("Creating {} using {}", node.getPath(), creator.getDescriptor());
 
         try {
-            creator.create(node, views);
+            RuleContext.run(creator.getDescriptor(), new Runnable() {
+                @Override
+                public void run() {
+                    creator.create(node, views);
+                }
+            });
         } catch (Exception e) {
             // TODO some representation of state of the inputs
             throw new ModelRuleExecutionException(creator.getDescriptor(), e);
@@ -539,17 +463,23 @@ public class DefaultModelRegistry implements ModelRegistry {
     }
 
     private <T> void fireMutation(MutatorRuleBinder<T> boundMutator) {
-        List<ModelView<?>> inputs = toViews(boundMutator.getInputBindings(), boundMutator.getAction());
+        final List<ModelView<?>> inputs = toViews(boundMutator.getInputBindings(), boundMutator.getAction());
 
-        ModelNodeInternal node = boundMutator.getSubjectBinding().getNode();
-        ModelAction<T> mutator = boundMutator.getAction();
+        final ModelNodeInternal node = boundMutator.getSubjectBinding().getNode();
+        final ModelAction<T> mutator = boundMutator.getAction();
         ModelRuleDescriptor descriptor = mutator.getDescriptor();
 
         LOGGER.debug("Mutating {} using {}", node.getPath(), mutator.getDescriptor());
 
-        ModelView<? extends T> view = assertView(node, boundMutator.getSubjectReference(), descriptor, inputs);
+        ModelReference<T> reference = Cast.uncheckedCast(boundMutator.getSubjectReference().getReference());
+        final ModelView<? extends T> view = assertView(node, reference, descriptor, inputs);
         try {
-            mutator.execute(node, view.getInstance(), inputs);
+            RuleContext.run(descriptor, new Runnable() {
+                @Override
+                public void run() {
+                    mutator.execute(node, view.getInstance(), inputs);
+                }
+            });
         } catch (Exception e) {
             // TODO some representation of state of the inputs
             throw new ModelRuleExecutionException(descriptor, e);
@@ -558,85 +488,32 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
     }
 
-    private List<ModelView<?>> toViews(List<ModelBinding<?>> bindings, ModelRule modelRule) {
+    private List<ModelView<?>> toViews(List<ModelBinding> bindings, ModelRule modelRule) {
         // hot path; create as little as possible…
         @SuppressWarnings("unchecked") ModelView<?>[] array = new ModelView<?>[bindings.size()];
         int i = 0;
-        for (ModelBinding<?> binding : bindings) {
-            closeRuleBinding(modelRule, binding);
-            ModelPath path = binding.getNode().getPath();
-            ModelNodeInternal element = require(path);
-            ModelView<?> view = assertView(element, binding.getReference().getType(), modelRule.getDescriptor(), "toViews");
+        for (ModelBinding binding : bindings) {
+            ModelNodeInternal element = binding.getNode();
+            ModelView<?> view = assertView(element, binding.getPredicate().getType(), modelRule.getDescriptor(), "toViews");
             array[i++] = view;
         }
         @SuppressWarnings("unchecked") List<ModelView<?>> views = Arrays.asList(array);
         return views;
     }
 
-    private void closeRuleBinding(ModelRule modelRule, ModelBinding<?> binding) {
-        if (rulesWithInputsBeingClosed.containsKey(modelRule)) {
-            throw ruleCycle(modelRule);
-        }
-        rulesWithInputsBeingClosed.put(modelRule, binding);
-        try {
-            close(binding.getNode());
-        } finally {
-            rulesWithInputsBeingClosed.remove(modelRule);
-        }
-    }
-
-    private ConfigurationCycleException ruleCycle(ModelRule cycleStartRule) {
-        boolean cycleStartFound = false;
-        String indent = "  ";
-        StringBuilder prefix = new StringBuilder(indent);
-        StringWriter out = new StringWriter();
-        PrintWriter writer = new PrintWriter(out);
-
-        writer.println("A cycle has been detected in model rule dependencies. References forming the cycle:");
-
-        for (Map.Entry<ModelRule, ModelBinding<?>> ruleInputInClosing : rulesWithInputsBeingClosed.entrySet()) {
-            ModelRule rule = ruleInputInClosing.getKey();
-            ModelRuleDescriptor ruleDescriptor = rule.getDescriptor();
-            ModelBinding<?> binding = ruleInputInClosing.getValue();
-            if (cycleStartFound) {
-                reportRuleInputBeingClosed(indent, prefix, writer, ruleDescriptor, binding);
-            } else {
-                if (rule.equals(cycleStartRule)) {
-                    cycleStartFound = true;
-                    reportRuleInputBeingClosed(indent, prefix, writer, ruleDescriptor, binding);
-                }
-            }
-        }
-        writer.print(cycleStartRule.getDescriptor().toString());
-
-        return new ConfigurationCycleException(out.toString());
-    }
-
-    private void reportRuleInputBeingClosed(String indent, StringBuilder prefix, PrintWriter writer, ModelRuleDescriptor ruleDescriptor, ModelBinding<?> binding) {
-        writer.print(ruleDescriptor.toString());
-        String referenceDescription = binding.getReference().getDescription();
-        if (referenceDescription != null) {
-            writer.print(" ");
-            writer.print(referenceDescription);
-        }
-        writer.print(" (path: ");
-        writer.print(binding.getNode().getPath().toString());
-        writer.print(")");
-        writer.println();
-        writer.print(prefix);
-        writer.print("\\--- ");
-        prefix.append(indent);
+    @Override
+    public MutableModelNode getRoot() {
+        return modelGraph.getRoot();
     }
 
     @Override
-    public ModelNode node(ModelPath path) {
+    public MutableModelNode node(ModelPath path) {
         return modelGraph.find(path);
     }
 
     @Override
     public void prepareForReuse() {
         reset = true;
-
         List<ModelNodeInternal> ephemerals = Lists.newLinkedList();
         collectEphemeralChildren(modelGraph.getRoot(), ephemerals);
         if (ephemerals.isEmpty()) {
@@ -662,6 +539,35 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
     }
 
+    private BindingPredicate mapSubject(ModelReference<?> subjectReference, ModelActionRole role, ModelPath scope) {
+        ModelReference<?> mappedReference;
+        if (subjectReference.getPath() == null) {
+            mappedReference = subjectReference.inScope(scope);
+        } else {
+            mappedReference = subjectReference.withPath(scope.descendant(subjectReference.getPath()));
+        }
+        if (role.getTargetState() != null) {
+            return new BindingPredicate(mappedReference.atState(role.getTargetState()));
+        } else {
+            return new AnyStateBindingPredicate(mappedReference);
+        }
+    }
+
+    private List<BindingPredicate> mapInputs(List<ModelReference<?>> inputs, ModelPath scope) {
+        if (inputs.isEmpty()) {
+            return Collections.emptyList();
+        }
+        ArrayList<BindingPredicate> result = new ArrayList<BindingPredicate>(inputs.size());
+        for (ModelReference<?> input : inputs) {
+            if (input.getPath() != null) {
+                result.add(new BindingPredicate(input.withPath(scope.descendant(input.getPath()))));
+            } else {
+                result.add(new BindingPredicate(input.inScope(ModelPath.ROOT)));
+            }
+        }
+        return result;
+    }
+
     private class ModelReferenceNode extends ModelNodeInternal {
         private ModelNodeInternal target;
 
@@ -710,6 +616,11 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
+        public <T> void applyToAllLinksTransitive(ModelActionRole type, ModelAction<T> action) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
         public <T> void applyToLink(ModelActionRole type, ModelAction<T> action) {
             throw new UnsupportedOperationException();
         }
@@ -720,7 +631,12 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
-        public <T> void applyToLinks(Class<T> type, Class<? extends RuleSource> rules) {
+        public void applyToLinks(ModelType<?> type, Class<? extends RuleSource> rules) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void applyToAllLinksTransitive(ModelType<?> type, Class<? extends RuleSource> rules) {
             throw new UnsupportedOperationException();
         }
 
@@ -756,6 +672,11 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
+        public int getLinkCount() {
+            return 0;
+        }
+
+        @Override
         public boolean hasLink(String name, ModelType<?> type) {
             return false;
         }
@@ -771,6 +692,11 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
+        public <T> void setPrivateData(Class<? super T> type, T object) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
         public <T> void setPrivateData(ModelType<? super T> type, T object) {
             throw new UnsupportedOperationException();
         }
@@ -781,17 +707,39 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
+        public <T> T getPrivateData(Class<T> type) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
         public void ensureUsable() {
         }
+
+        @Override
+        public void realize() {
+
+        }
+
+        @Override
+        public MutableModelNode getParent() {
+            return null;
+        }
     }
 
     private class ModelElementNode extends ModelNodeInternal {
         private final Map<String, ModelNodeInternal> links = Maps.newTreeMap();
+        private final MutableModelNode parent;
         private Object privateData;
         private ModelType<?> privateDataType;
 
-        public ModelElementNode(CreatorRuleBinder creatorBinder) {
-            super(creatorBinder);
+        public ModelElementNode(CreatorRuleBinder creatorRuleBinder, MutableModelNode parent) {
+            super(creatorRuleBinder);
+            this.parent = parent;
+        }
+
+        @Override
+        public MutableModelNode getParent() {
+            return parent;
         }
 
         @Override
@@ -800,6 +748,11 @@ public class DefaultModelRegistry implements ModelRegistry {
             return node;
         }
 
+        @Override
+        public <T> T getPrivateData(Class<T> type) {
+            return getPrivateData(ModelType.of(type));
+        }
+
         public <T> T getPrivateData(ModelType<T> type) {
             if (privateData == null) {
                 return null;
@@ -816,6 +769,11 @@ public class DefaultModelRegistry implements ModelRegistry {
             return privateData;
         }
 
+        @Override
+        public <T> void setPrivateData(Class<? super T> type, T object) {
+            setPrivateData(ModelType.of(type), object);
+        }
+
         public <T> void setPrivateData(ModelType<? super T> type, T object) {
             if (!isMutable()) {
                 throw new IllegalStateException(String.format("Cannot set value for model element '%s' as this element is not mutable.", getPath()));
@@ -870,6 +828,11 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
+        public int getLinkCount() {
+            return links.size();
+        }
+
+        @Override
         public boolean hasLink(String name, ModelType<?> type) {
             ModelNodeInternal linked = getLink(name);
             return linked != null && linked.getPromise().canBeViewedAsWritable(type);
@@ -902,19 +865,40 @@ public class DefaultModelRegistry implements ModelRegistry {
         }
 
         @Override
-        public <T> void applyToLinks(Class<T> type, final Class<? extends RuleSource> rules) {
-            final ModelType<T> modelType = ModelType.of(type);
+        public void applyToLinks(final ModelType<?> type, final Class<? extends RuleSource> rules) {
             registerListener(new ModelCreationListener() {
                 @Nullable
                 @Override
-                public ModelPath matchParent() {
+                public ModelPath getParent() {
                     return getPath();
                 }
 
                 @Nullable
                 @Override
-                public ModelType<?> matchType() {
-                    return modelType;
+                public ModelType<?> getType() {
+                    return type;
+                }
+
+                @Override
+                public boolean onCreate(ModelNodeInternal node) {
+                    node.applyToSelf(rules);
+                    return false;
+                }
+            });
+        }
+
+        @Override
+        public void applyToAllLinksTransitive(final ModelType<?> type, final Class<? extends RuleSource> rules) {
+            registerListener(new ModelCreationListener() {
+                @Override
+                public ModelPath getAncestor() {
+                    return ModelElementNode.this.getPath();
+                }
+
+                @Nullable
+                @Override
+                public ModelType<?> getType() {
+                    return type;
                 }
 
                 @Override
@@ -928,7 +912,6 @@ public class DefaultModelRegistry implements ModelRegistry {
         public void apply(Class<? extends RuleSource> rules, ModelPath scope) {
             Iterable<ExtractedModelRule> extractedRules = ruleExtractor.extract(rules);
             for (ExtractedModelRule extractedRule : extractedRules) {
-                // TODO - remove this when we remove the 'rule dependencies' mechanism
                 if (!extractedRule.getRuleDependencies().isEmpty()) {
                     throw new IllegalStateException("Rule source " + rules + " cannot have plugin dependencies (introduced by rule " + extractedRule + ")");
                 }
@@ -940,7 +923,6 @@ public class DefaultModelRegistry implements ModelRegistry {
                         throw new InvalidModelRuleDeclarationException("Rule " + extractedRule.getCreator().getDescriptor() + " cannot be applied at the scope of model element " + scope + " as creation rules cannot be used when applying rule sources to particular elements");
                     }
                 } else if (extractedRule.getType().equals(ExtractedModelRule.Type.ACTION)) {
-                    // TODO this is a roundabout path, something like the registrar interface should be implementable by the regsitry and nodes
                     bind(extractedRule.getActionRole(), extractedRule.getAction(), scope);
                 } else {
                     throw new IllegalStateException("unexpected extracted rule type: " + extractedRule.getType());
@@ -957,13 +939,40 @@ public class DefaultModelRegistry implements ModelRegistry {
             registerListener(new ModelCreationListener() {
                 @Nullable
                 @Override
-                public ModelPath matchParent() {
-                    return getPath();
+                public ModelPath getParent() {
+                    return ModelElementNode.this.getPath();
                 }
 
                 @Nullable
                 @Override
-                public ModelType<?> matchType() {
+                public ModelType<?> getType() {
+                    return action.getSubject().getType();
+                }
+
+                @Override
+                public boolean onCreate(ModelNodeInternal node) {
+                    bind(ModelReference.of(node.getPath(), action.getSubject().getType()), type, action, ModelPath.ROOT);
+                    return false;
+                }
+            });
+        }
+
+        @Override
+        public <T> void applyToAllLinksTransitive(final ModelActionRole type, final ModelAction<T> action) {
+            if (action.getSubject().getPath() != null) {
+                throw new IllegalArgumentException("Linked element action reference must have null path.");
+            }
+
+            registerListener(new ModelCreationListener() {
+                @Nullable
+                @Override
+                public ModelPath getAncestor() {
+                    return ModelElementNode.this.getPath();
+                }
+
+                @Nullable
+                @Override
+                public ModelType<?> getType() {
                     return action.getSubject().getType();
                 }
 
@@ -980,7 +989,7 @@ public class DefaultModelRegistry implements ModelRegistry {
             if (!getPath().isDirectChild(creator.getPath())) {
                 throw new IllegalArgumentException(String.format("Reference element creator has a path (%s) which is not a child of this node (%s).", creator.getPath(), getPath()));
             }
-            registerNode(this, new ModelReferenceNode(toCreatorBinder(creator)));
+            registerNode(this, new ModelReferenceNode(toCreatorBinder(creator, ModelPath.ROOT)));
         }
 
         @Override
@@ -988,7 +997,7 @@ public class DefaultModelRegistry implements ModelRegistry {
             if (!getPath().isDirectChild(creator.getPath())) {
                 throw new IllegalArgumentException(String.format("Linked element creator has a path (%s) which is not a child of this node (%s).", creator.getPath(), getPath()));
             }
-            registerNode(this, new ModelElementNode(toCreatorBinder(creator)));
+            registerNode(this, new ModelElementNode(toCreatorBinder(creator, ModelPath.ROOT), this));
         }
 
         @Override
@@ -1012,6 +1021,448 @@ public class DefaultModelRegistry implements ModelRegistry {
         public void ensureUsable() {
             transition(this, Initialized, true);
         }
+
+        @Override
+        public void realize() {
+            close(this);
+        }
+    }
+
+    private class GoalGraph {
+        private final Map<NodeAtState, ModelGoal> nodeStates = new HashMap<NodeAtState, ModelGoal>();
+
+        public ModelGoal nodeAtState(NodeAtState goal) {
+            ModelGoal node = nodeStates.get(goal);
+            if (node == null) {
+                switch (goal.state) {
+                    case Known:
+                        node = new MakeKnown(goal.path);
+                        break;
+                    case Created:
+                        node = new Create(goal);
+                        break;
+                    case GraphClosed:
+                        node = new CloseGraph(goal);
+                        break;
+                    default:
+                        node = new ApplyActions(goal);
+                }
+                nodeStates.put(goal, node);
+            }
+            return node;
+        }
+    }
+
+    /**
+     * Some abstract goal that must be achieved in the model graph.
+     */
+    private abstract static class ModelGoal {
+        enum State {
+            NotSeen,
+            DiscoveringDependencies,
+            VisitingDependencies,
+            Achieved,
+        }
+
+        public State state = State.NotSeen;
+
+        /**
+         * Determines whether the goal has already been achieved. Invoked prior to traversing any dependencies of this goal, and if true is returned the dependencies of this goal are not traversed and
+         * the action not applied.
+         */
+        public boolean isAchieved() {
+            return false;
+        }
+
+        /**
+         * Invoked prior to calculating dependencies.
+         */
+        public void attachNode() {
+        }
+
+        /**
+         * Calculates any dependencies for this goal. May be invoked multiple times, should only add newly dependencies discovered dependencies on each invocation.
+         *
+         * <p>The dependencies returned by this method are all traversed before this method is called another time.</p>
+         *
+         * @return true if this goal will be ready to apply once the returned dependencies have been achieved. False if additional dependencies for this goal may be discovered during the execution of
+         * the known dependencies.
+         */
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            return true;
+        }
+
+        /**
+         * Applies the action of this goal.
+         */
+        void apply() {
+        }
+
+        void attachToCycle(List<String> displayValue) {
+        }
+    }
+
+    /**
+     * Some abstract goal to be achieve for a particular node in the model graph.
+     */
+    private abstract class ModelNodeGoal extends ModelGoal {
+        public final ModelPath target;
+        public ModelNodeInternal node;
+
+        protected ModelNodeGoal(ModelPath target) {
+            this.target = target;
+        }
+
+        public ModelPath getPath() {
+            return target;
+        }
+
+        @Override
+        public final boolean isAchieved() {
+            node = modelGraph.find(target);
+            return node != null && doIsAchieved();
+        }
+
+        /**
+         * Invoked only if node is known prior to traversing dependencies of this goal
+         */
+        protected boolean doIsAchieved() {
+            return false;
+        }
+
+        @Override
+        public void attachNode() {
+            if (node != null) {
+                return;
+            }
+            node = modelGraph.find(getPath());
+        }
+    }
+
+    private class MakeKnown extends ModelNodeGoal {
+        public MakeKnown(ModelPath path) {
+            super(path);
+        }
+
+        @Override
+        public String toString() {
+            return "make known " + getPath() + ", state: " + state;
+        }
+
+        @Override
+        public boolean doIsAchieved() {
+            // Only called when node exists, therefore node is known
+            return true;
+        }
+
+        @Override
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            // Not already known, attempt to self-close the parent
+            ModelPath parent = getPath().getParent();
+            if (parent != null) {
+                // TODO - should be >= self closed
+                dependencies.add(graph.nodeAtState(new NodeAtState(parent, SelfClosed)));
+            }
+            return true;
+        }
+    }
+
+    private abstract class TransitionNodeToState extends ModelNodeGoal {
+        final NodeAtState target;
+        private boolean seenPredecessor;
+
+        public TransitionNodeToState(NodeAtState target) {
+            super(target.path);
+            this.target = target;
+        }
+
+        @Override
+        public String toString() {
+            return "transition " + getPath() + ", target: " + target.state + ", state: " + state;
+        }
+
+        public ModelNode.State getTargetState() {
+            return target.state;
+        }
+
+        @Override
+        public boolean doIsAchieved() {
+            return node.getState().compareTo(getTargetState()) >= 0;
+        }
+
+        @Override
+        public final boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            if (!seenPredecessor) {
+                // Node must be at the predecessor state before calculating dependencies
+                NodeAtState predecessor = new NodeAtState(getPath(), getTargetState().previous());
+                dependencies.add(graph.nodeAtState(predecessor));
+                // Transition any other nodes that depend on the predecessor state
+                dependencies.add(new TransitionDependents(predecessor));
+                seenPredecessor = true;
+                return false;
+            }
+            if (node == null) {
+                throw new IllegalStateException(String.format("Cannot transition model element '%s' to state %s as it does not exist.", getPath(), getTargetState().name()));
+            }
+            return doCalculateDependencies(graph, dependencies);
+        }
+
+        boolean doCalculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            return true;
+        }
+
+        @Override
+        public final void apply() {
+            if (!node.getState().equals(getTargetState().previous())) {
+                throw new IllegalStateException(String.format("Cannot transition model element '%s' to state %s as it is already at state %s.", node.getPath(), getTargetState(), node.getState()));
+            }
+            LOGGER.debug("Transitioning model element '{}' to state {}.", node.getPath(), getTargetState().name());
+            doApply(node);
+            node.setState(getTargetState());
+        }
+
+        abstract void doApply(ModelNodeInternal node);
+
+        @Override
+        void attachToCycle(List<String> displayValue) {
+            displayValue.add(getPath().toString());
+        }
+    }
+
+    private class Create extends TransitionNodeToState {
+        public Create(NodeAtState target) {
+            super(target);
+        }
+
+        @Override
+        boolean doCalculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            // Must run the creator
+            dependencies.add(new RunCreatorAction(getPath(), node.getCreatorBinder()));
+            return true;
+        }
+
+        @Override
+        void doApply(ModelNodeInternal node) {
+            // Nothing to do, creator action is run as a dependency
+        }
+    }
+
+    private class TransitionDependents extends ModelGoal {
+        private final NodeAtState input;
+
+        public TransitionDependents(NodeAtState input) {
+            this.input = input;
+        }
+
+        @Override
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            for (RuleBinder rule : ruleBindings.getRulesWithInput(input)) {
+                if (rule.getSubjectBinding() == null || !rule.getSubjectBinding().isBound() || rule.getSubjectReference().getState() == null) {
+                    // TODO - implement these cases
+                    continue;
+                }
+                if (rule.getSubjectBinding().getNode().getPath().equals(input.path)) {
+                    // Ignore future states of the input node
+                    continue;
+                }
+                dependencies.add(graph.nodeAtState(new NodeAtState(rule.getSubjectBinding().getNode().getPath(), rule.getSubjectReference().getState())));
+            }
+            return true;
+        }
+    }
+
+    private class ApplyActions extends TransitionNodeToState {
+        private final Set<RuleBinder> seenRules = new HashSet<RuleBinder>();
+
+        public ApplyActions(NodeAtState target) {
+            super(target);
+        }
+
+        @Override
+        boolean doCalculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            if (seenRules.isEmpty() && getTargetState().equals(DefaultsApplied)) {
+                for (RuleBinder binder : ruleBindings.getRulesWithSubject(getPath())) {
+                    if (seenRules.add(binder)) {
+                        MutatorRuleBinder<?> mutator = Cast.uncheckedCast(binder);
+                        dependencies.add(new RunModelAction(getPath(), mutator));
+                    }
+                }
+            }
+
+            // Must run each action
+            for (RuleBinder binder : ruleBindings.getRulesWithSubject(target)) {
+                if (seenRules.add(binder)) {
+                    MutatorRuleBinder<?> mutator = Cast.uncheckedCast(binder);
+                    dependencies.add(new RunModelAction(getPath(), mutator));
+                }
+            }
+            return false;
+        }
+
+        @Override
+        void doApply(ModelNodeInternal node) {
+            // Nothing to do, actions are run as dependencies
+        }
+    }
+
+    private class CloseGraph extends TransitionNodeToState {
+        public CloseGraph(NodeAtState target) {
+            super(target);
+        }
+
+        @Override
+        boolean doCalculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            // Must graph-close each child first
+            for (ModelNodeInternal child : node.getLinks()) {
+                dependencies.add(graph.nodeAtState(new NodeAtState(child.getPath(), GraphClosed)));
+            }
+            return true;
+        }
+
+        @Override
+        void doApply(ModelNodeInternal node) {
+            // Nothing to do
+        }
+    }
+
+    /**
+     * Attempts to define the children of a given node. Does not fail if not possible to do so.
+     */
+    private class TryDefineChildren extends ModelGoal {
+        private final ModelPath path;
+        private boolean attemptedParent;
+
+        public TryDefineChildren(ModelPath path) {
+            this.path = path;
+        }
+
+        public ModelPath getPath() {
+            return path;
+        }
+
+        @Override
+        public boolean isAchieved() {
+            ModelNodeInternal node = modelGraph.find(path);
+            return node != null && node.getState().compareTo(SelfClosed) >= 0;
+        }
+
+        @Override
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            if (path.getParent() != null && !attemptedParent) {
+                dependencies.add(new TryDefineChildren(path.getParent()));
+                attemptedParent = true;
+                return false;
+            }
+            if (modelGraph.find(path) != null) {
+                dependencies.add(graph.nodeAtState(new NodeAtState(path, SelfClosed)));
+            }
+            return true;
+        }
     }
 
+    /**
+     * Attempts to bind the inputs of a rule. Does not fail if not possible to bind all inputs.
+     */
+    private class TryBindInputs extends ModelGoal {
+        private final RuleBinder binder;
+
+        public TryBindInputs(RuleBinder binder) {
+            this.binder = binder;
+        }
+
+        @Override
+        public String toString() {
+            return "bind inputs for " + binder.getDescriptor() + ", state: " + state;
+        }
+
+        @Override
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            if (binder.getSubjectBinding() != null) {
+                // Shouldn't really be here. Currently this goal is used by {@link #bindAllReferences} which also expects the subject to be bound
+                maybeBind(binder.getSubjectBinding(), dependencies);
+            }
+            for (ModelBinding binding : binder.getInputBindings()) {
+                maybeBind(binding, dependencies);
+            }
+            return true;
+        }
+
+        private void maybeBind(ModelBinding binding, Collection<ModelGoal> dependencies) {
+            if (!binding.isBound()) {
+                if (binding.getPredicate().getPath() != null) {
+                    dependencies.add(new TryDefineChildren(binding.getPredicate().getPath().getParent()));
+                } else {
+                    dependencies.add(new TryDefineChildren(binding.getPredicate().getScope()));
+                }
+            }
+        }
+    }
+
+    private abstract class RunAction extends ModelNodeGoal {
+        private final RuleBinder binder;
+        private boolean bindInputs;
+
+        public RunAction(ModelPath path, RuleBinder binder) {
+            super(path);
+            this.binder = binder;
+        }
+
+        @Override
+        public String toString() {
+            return "run action for " + getPath() + ", rule: " + binder.getDescriptor() + ", state: " + state;
+        }
+
+        @Override
+        public boolean calculateDependencies(GoalGraph graph, Collection<ModelGoal> dependencies) {
+            if (!bindInputs) {
+                // Must prepare to bind inputs first
+                dependencies.add(new TryBindInputs(binder));
+                bindInputs = true;
+                return false;
+            }
+            // Must close each input first
+            if (!binder.isBound()) {
+                throw unbound(Collections.singleton(binder));
+            }
+            for (ModelBinding binding : binder.getInputBindings()) {
+                dependencies.add(graph.nodeAtState(new NodeAtState(binding.getNode().getPath(), binding.getPredicate().getState())));
+            }
+            return true;
+        }
+
+        @Override
+        void attachToCycle(List<String> displayValue) {
+            displayValue.add(binder.getDescriptor().toString());
+        }
+    }
+
+    private class RunCreatorAction extends RunAction {
+        public RunCreatorAction(ModelPath path, CreatorRuleBinder binder) {
+            super(path, binder);
+        }
+
+        @Override
+        void apply() {
+            CreatorRuleBinder creatorBinder = node.getCreatorBinder();
+            LOGGER.debug("Running model element '{}' creator rule action {}", getPath(), creatorBinder.getDescriptor());
+            doCreate(node, creatorBinder);
+            node.notifyFired(creatorBinder);
+        }
+    }
+
+    private class RunModelAction extends RunAction {
+        private final MutatorRuleBinder<?> binder;
+
+        public RunModelAction(ModelPath path, MutatorRuleBinder<?> binder) {
+            super(path, binder);
+            this.binder = binder;
+        }
+
+        @Override
+        void apply() {
+            LOGGER.debug("Running model element '{}' rule action {}", getPath(), binder.getDescriptor());
+            fireMutation(binder);
+            node.notifyFired(binder);
+        }
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelBinding.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelBinding.java
index 6674db6..0fac811 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelBinding.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelBinding.java
@@ -16,56 +16,56 @@
 
 package org.gradle.model.internal.registry;
 
-import com.google.common.base.Function;
-import net.jcip.annotations.ThreadSafe;
-import org.gradle.api.Nullable;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.ModelPromise;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 
 /**
  * A binding of a reference to an actual model element.
  * <p>
  * A binding represents the knowledge that the model element referenced by the reference is known and can project a view of the reference type.
- * Like the reference, whether the view is read or write is not inherent in the binding and is contextual.
  */
- at ThreadSafe
-class ModelBinding<T> {
+abstract class ModelBinding {
 
-    private final ModelNodeInternal node;
-    private final ModelReference<T> reference;
+    final BindingPredicate predicate;
+    final ModelRuleDescriptor referrer;
+    final boolean writable;
+    protected ModelNodeInternal boundTo;
 
-    private ModelBinding(ModelReference<T> reference, ModelNodeInternal node) {
-        this.node = node;
-        this.reference = reference;
+    protected ModelBinding(ModelRuleDescriptor referrer, BindingPredicate predicate, boolean writable) {
+        this.predicate = predicate;
+        this.referrer = referrer;
+        this.writable = writable;
     }
 
-    public static <T> ModelBinding<T> of(ModelReference<T> reference, ModelNodeInternal modelNode) {
-        return new ModelBinding<T>(reference, modelNode);
+    public BindingPredicate getPredicate() {
+        return predicate;
     }
 
-    public ModelReference<T> getReference() {
-        return reference;
+    public boolean isBound() {
+        return boundTo != null;
     }
 
     public ModelNodeInternal getNode() {
-        return node;
+        if (boundTo == null) {
+            throw new IllegalStateException("Target node has not been bound.");
+        }
+        return boundTo;
+    }
+
+    boolean isTypeCompatible(ModelPromise promise) {
+        return promise.canBeViewedAsWritable(predicate.getType()) || promise.canBeViewedAsReadOnly(predicate.getType());
     }
 
     @Override
     public String toString() {
-        return "ModelBinding{reference=" + reference + ", node=" + node + '}';
+        return "ModelBinding{predicate=" + predicate + ", node=" + boundTo + '}';
     }
 
-    public static class GetPath implements Function<ModelBinding<?>, ModelPath> {
-
-        public static final Function<ModelBinding<?>, ModelPath> INSTANCE = new GetPath();
-
-        private GetPath() {
-        }
+    public abstract void onCreate(ModelNodeInternal node);
 
-        @Nullable
-        public ModelPath apply(ModelBinding<?> input) {
-            return input.getNode().getPath();
+    public void onRemove(ModelNodeInternal node) {
+        if (node == boundTo) {
+            boundTo = null;
         }
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelCreationListener.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelCreationListener.java
index b50c207..142162c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelCreationListener.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelCreationListener.java
@@ -16,49 +16,14 @@
 
 package org.gradle.model.internal.registry;
 
-import org.gradle.api.Nullable;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.core.ModelPredicate;
 
-public abstract class ModelCreationListener {
+abstract class ModelCreationListener extends ModelPredicate {
     /**
-     * Returns the path of the node which this listener is interested in, or null if path is not relevant.
-     */
-    @Nullable
-    public ModelPath matchPath() {
-        return null;
-    }
-
-    /**
-     * Returns the parent path of the node which this listener is interested in, or null if path is not relevant.
-     */
-    @Nullable
-    public ModelPath matchParent() {
-        return null;
-    }
-
-    /**
-     * Return the path of the scope this listener is interested in, or null if the scope is not relevant.
+     * Invoked for each node that matches the criteria specified by {@link #getPath()}, {@link #getParent()}, {@link #getAncestor()} <em>and</em> {@link #getType()},
+     * or every node if no criteria specified. Stops notifying listener with further nodes when this method returns true.
      *
-     * If the returned value is not null then the listener will be informed about element created at the returned path and about its immediate children if other criteria specified by this listener
-     * match as well.
-     */
-    @Nullable
-    public ModelPath matchScope() {
-        return null;
-    }
-
-    /**
-     * Returns the type of node which this listener is interested in, or null if type is not relevant.
-     */
-    @Nullable
-    public ModelType<?> matchType() {
-        return null;
-    }
-
-    /**
-     * Invoked for each node that matches the criteria specified by {@link #matchPath()}, {@link #matchParent()}, {@link #matchScope()} or {@link #matchType()}, or every node if
-     * no criteria specified. Stops notifying listener with further nodes when this method returns true.
+     * @return true if this listener should no longer receive any notifications of additional nodes.
      */
     public abstract boolean onCreate(ModelNodeInternal node);
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelGraph.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelGraph.java
index afe8cb8..0a4a8d1 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelGraph.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelGraph.java
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.registry;
 
+import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.SetMultimap;
@@ -25,12 +26,12 @@ import org.gradle.model.internal.core.ModelPath;
 
 import java.util.*;
 
-public class ModelGraph {
+class ModelGraph {
     private final ModelNodeInternal root;
     private final Map<ModelPath, ModelNodeInternal> flattened = Maps.newTreeMap();
     private final SetMultimap<ModelPath, ModelCreationListener> pathListeners = LinkedHashMultimap.create();
     private final SetMultimap<ModelPath, ModelCreationListener> parentListeners = LinkedHashMultimap.create();
-    private final SetMultimap<ModelPath, ModelCreationListener> scopeListeners = LinkedHashMultimap.create();
+    private final SetMultimap<ModelPath, ModelCreationListener> ancestorListeners = LinkedHashMultimap.create();
     private final Set<ModelCreationListener> listeners = new LinkedHashSet<ModelCreationListener>();
     private boolean notifying;
     private final List<ModelCreationListener> pendingListeners = new ArrayList<ModelCreationListener>();
@@ -65,9 +66,13 @@ public class ModelGraph {
         try {
             notifyListeners(node, pathListeners.get(node.getPath()));
             notifyListeners(node, parentListeners.get(node.getPath().getParent()));
-            notifyListeners(node, scopeListeners.get(node.getPath()));
-            notifyListeners(node, scopeListeners.get(node.getPath().getParent()));
             notifyListeners(node, listeners);
+            if (!ancestorListeners.isEmpty()) {
+                // Don't traverse path back to root when there is nothing that can possibly match
+                for (ModelPath path = node.getPath().getParent(); path != null; path = path.getParent()) {
+                    notifyListeners(node, ancestorListeners.get(path));
+                }
+            }
         } finally {
             notifying = false;
         }
@@ -96,52 +101,77 @@ public class ModelGraph {
     private void doAddListener(ModelCreationListener listener) {
         notifying = true;
         try {
-            if (listener.matchPath() != null) {
-                ModelNodeInternal node = flattened.get(listener.matchPath());
-                if (node != null) {
-                    if (maybeNotify(node, listener)) {
-                        return;
-                    }
-                }
-                pathListeners.put(listener.matchPath(), listener);
+            if (listener.getPath() != null) {
+                addPathListener(listener);
                 return;
             }
-            if (listener.matchParent() != null) {
-                ModelNodeInternal parent = flattened.get(listener.matchParent());
-                if (parent != null) {
-                    for (ModelNodeInternal node : parent.getLinks()) {
-                        if (maybeNotify(node, listener)) {
-                            return;
-                        }
-                    }
-                }
-                parentListeners.put(listener.matchParent(), listener);
+            if (listener.getParent() != null) {
+                addParentListener(listener);
                 return;
             }
-            if (listener.matchScope() != null) {
-                ModelNodeInternal scope = flattened.get(listener.matchScope());
-                if (scope != null) {
-                    if (maybeNotify(scope, listener)) {
+            if (listener.getAncestor() != null) {
+                addAncestorListener(listener);
+                return;
+            }
+            addEverythingListener(listener);
+        } finally {
+            notifying = false;
+        }
+    }
+
+    private void addEverythingListener(ModelCreationListener listener) {
+        for (ModelNodeInternal node : flattened.values()) {
+            if (maybeNotify(node, listener)) {
+                return;
+            }
+        }
+        listeners.add(listener);
+    }
+
+    private void addAncestorListener(ModelCreationListener listener) {
+        if (listener.getAncestor().equals(ModelPath.ROOT)) {
+            // Don't need to match on path
+            addEverythingListener(listener);
+            return;
+        }
+
+        ModelNodeInternal ancestor = flattened.get(listener.getAncestor());
+        if (ancestor != null) {
+            LinkedList<ModelNodeInternal> queue = new LinkedList<ModelNodeInternal>();
+            queue.add(ancestor);
+            while (!queue.isEmpty()) {
+                ModelNodeInternal parent = queue.removeFirst();
+                for (ModelNodeInternal node : parent.getLinks()) {
+                    if (maybeNotify(node, listener)) {
                         return;
                     }
-                    for (ModelNodeInternal node : scope.getLinks()) {
-                        if (maybeNotify(node, listener)) {
-                            return;
-                        }
-                    }
+                    queue.addFirst(node);
                 }
-                scopeListeners.put(listener.matchScope(), listener);
-                return;
             }
-            for (ModelNodeInternal node : flattened.values()) {
+        }
+        ancestorListeners.put(listener.getAncestor(), listener);
+    }
+
+    private void addParentListener(ModelCreationListener listener) {
+        ModelNodeInternal parent = flattened.get(listener.getParent());
+        if (parent != null) {
+            for (ModelNodeInternal node : parent.getLinks()) {
                 if (maybeNotify(node, listener)) {
                     return;
                 }
             }
-            listeners.add(listener);
-        } finally {
-            notifying = false;
         }
+        parentListeners.put(listener.getParent(), listener);
+    }
+
+    private void addPathListener(ModelCreationListener listener) {
+        ModelNodeInternal node = flattened.get(listener.getPath());
+        if (node != null) {
+            if (maybeNotify(node, listener)) {
+                return;
+            }
+        }
+        pathListeners.put(listener.getPath(), listener);
     }
 
     private void flush() {
@@ -154,7 +184,7 @@ public class ModelGraph {
     }
 
     private boolean maybeNotify(ModelNodeInternal node, ModelCreationListener listener) {
-        if (listener.matchType() != null && !node.getPromise().canBeViewedAsWritable(listener.matchType()) && !node.getPromise().canBeViewedAsReadOnly(listener.matchType())) {
+        if (listener.getType() != null && !node.getPromise().canBeViewedAsWritable(listener.getType()) && !node.getPromise().canBeViewedAsReadOnly(listener.getType())) {
             return false;
         }
         return listener.onCreate(node);
@@ -174,6 +204,14 @@ public class ModelGraph {
         return found;
     }
 
+    public Iterable<ModelNodeInternal> findAllInScope(ModelPath scope) {
+        ModelNodeInternal node = flattened.get(scope);
+        if (node == null) {
+            return Collections.emptyList();
+        }
+        return Iterables.concat(Collections.singleton(node), node.getLinks());
+    }
+
     @Nullable
     public ModelNodeInternal remove(ModelNode node) {
         ModelNodeInternal parentNode = find(node.getPath().getParent());
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelNodeInternal.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelNodeInternal.java
index b16052d..0a999b8 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelNodeInternal.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelNodeInternal.java
@@ -17,8 +17,11 @@
 package org.gradle.model.internal.registry;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import com.google.common.collect.Multimaps;
 import com.google.common.collect.Sets;
 import org.gradle.api.Nullable;
 import org.gradle.model.internal.core.*;
@@ -27,14 +30,23 @@ import org.gradle.model.internal.type.ModelType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Set;
 
 abstract class ModelNodeInternal implements MutableModelNode {
 
+    private static final Supplier<List<MutatorRuleBinder<?>>> LIST_SUPPLIER = new Supplier<List<MutatorRuleBinder<?>>>() {
+        @Override
+        public List<MutatorRuleBinder<?>> get() {
+            return Lists.newArrayList();
+        }
+    };
+
     private static final Logger LOGGER = LoggerFactory.getLogger(ModelNodeInternal.class);
 
     private CreatorRuleBinder creatorBinder;
-    private Map<ModelActionRole, List<MutatorRuleBinder<?>>> mutators;
     private final Set<ModelNodeInternal> dependencies = Sets.newHashSet();
     private final Set<ModelNodeInternal> dependents = Sets.newHashSet();
     private ModelNode.State state = ModelNode.State.Known;
@@ -85,61 +97,13 @@ abstract class ModelNodeInternal implements MutableModelNode {
         return creatorBinder.getCreator().isEphemeral();
     }
 
-    public void addMutatorBinder(ModelActionRole role, MutatorRuleBinder<?> mutator) {
-        if (mutators == null) {
-            mutators = Maps.newEnumMap(ModelActionRole.class);
-        }
-
-        List<MutatorRuleBinder<?>> mutatorsForRole = mutators.get(role);
-        if (mutatorsForRole == null) {
-            mutatorsForRole = Lists.newLinkedList();
-            mutators.put(role, mutatorsForRole);
-        }
-
-        mutatorsForRole.add(mutator);
-    }
-
-    public Iterable<MutatorRuleBinder<?>> getMutatorBinders(ModelActionRole role) {
-        if (mutators == null) {
-            return Collections.emptyList();
-        }
-        final List<MutatorRuleBinder<?>> ruleBinders = mutators.get(role);
-        if (ruleBinders == null) {
-            return Collections.emptyList();
-        } else {
-            return new Iterable<MutatorRuleBinder<?>>() {
-                @Override
-                public Iterator<MutatorRuleBinder<?>> iterator() {
-                    return new Iterator<MutatorRuleBinder<?>>() {
-                        int i;
-
-                        @Override
-                        public void remove() {
-                            throw new UnsupportedOperationException();
-                        }
-
-                        @Override
-                        public boolean hasNext() {
-                            return i < ruleBinders.size();
-                        }
-
-                        @Override
-                        public MutatorRuleBinder<?> next() {
-                            if (hasNext()) {
-                                return ruleBinders.get(i++);
-                            } else {
-                                throw new NoSuchElementException();
-                            }
-                        }
-                    };
-                }
-            };
-        }
+    private static ListMultimap<ModelNode.State, MutatorRuleBinder<?>> createMutatorsMap() {
+        return Multimaps.newListMultimap(new EnumMap<ModelNode.State, Collection<MutatorRuleBinder<?>>>(ModelNode.State.class), LIST_SUPPLIER);
     }
 
     public void notifyFired(RuleBinder binder) {
         assert binder.isBound();
-        for (ModelBinding<?> inputBinding : binder.getInputBindings()) {
+        for (ModelBinding inputBinding : binder.getInputBindings()) {
             ModelNodeInternal node = inputBinding.getNode();
             dependencies.add(node);
             node.dependents.add(this);
@@ -174,8 +138,8 @@ abstract class ModelNodeInternal implements MutableModelNode {
         return state.mutable;
     }
 
-    public boolean canApply(ModelActionRole type) {
-        return type.ordinal() >= state.ordinal() - ModelNode.State.Created.ordinal();
+    public boolean canApply(ModelNode.State targetState) {
+        return state.compareTo(targetState) < 0;
     }
 
     public ModelPromise getPromise() {
@@ -236,4 +200,11 @@ abstract class ModelNodeInternal implements MutableModelNode {
             }
         }
     }
+
+    @Nullable
+    @Override
+    public Optional<String> getValueDescription() {
+        this.ensureUsable();
+        return this.getAdapter().getValueDescription(this);
+    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelRegistry.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelRegistry.java
index a9ac62a..8be1356 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelRegistry.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelRegistry.java
@@ -21,7 +21,7 @@ import org.gradle.model.RuleSource;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.type.ModelType;
 
-public interface ModelRegistry extends ModelRegistrar {
+public interface ModelRegistry {
 
     /**
      * Get the fully defined model element at the given path as the given type.
@@ -33,7 +33,7 @@ public interface ModelRegistry extends ModelRegistrar {
      * @param <T> the type to project the node as
      * @return the node as the given type
      */
-    public <T> T realize(ModelPath path, ModelType<T> type);
+    <T> T realize(ModelPath path, ModelType<T> type);
 
     /**
      * Get the fully defined model element at the given path.
@@ -73,7 +73,7 @@ public interface ModelRegistry extends ModelRegistrar {
      * @return the node at the desired state, or null if node is unknown
      */
     @Nullable
-    public ModelNode atState(ModelPath path, ModelNode.State state);
+    ModelNode atState(ModelPath path, ModelNode.State state);
 
     /**
      * Returns the node at the given path at the desired state or later, if it exists.
@@ -88,16 +88,14 @@ public interface ModelRegistry extends ModelRegistrar {
      * @return the node at the desired state, or null if node is unknown
      */
     @Nullable
-    public ModelNode atStateOrLater(ModelPath path, ModelNode.State state);
+    ModelNode atStateOrLater(ModelPath path, ModelNode.State state);
 
-    public ModelNode.State state(ModelPath path);
+    ModelNode.State state(ModelPath path);
 
     void remove(ModelPath path);
 
-    @Override
     ModelRegistry replace(ModelCreator newCreator);
 
-    @Override
     ModelRegistry createOrReplace(ModelCreator newCreator);
 
     /**
@@ -118,16 +116,16 @@ public interface ModelRegistry extends ModelRegistrar {
      */
     void bindAllReferences() throws UnboundModelRulesException;
 
-    @Override
     ModelRegistry create(ModelCreator creator);
 
-    @Override
     <T> ModelRegistry configure(ModelActionRole role, ModelAction<T> action);
 
     ModelRegistry apply(Class<? extends RuleSource> rules);
 
+    MutableModelNode getRoot();
+
     @Nullable
-    ModelNode node(ModelPath path);
+    MutableModelNode node(ModelPath path);
 
     /**
      * Resets the state of the model registry, discarding all ephemeral state.
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/MutatorRuleBinder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/MutatorRuleBinder.java
index f09b0ff..b287074 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/MutatorRuleBinder.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/MutatorRuleBinder.java
@@ -16,52 +16,47 @@
 
 package org.gradle.model.internal.registry;
 
+import org.gradle.api.Action;
 import org.gradle.model.internal.core.ModelAction;
-import org.gradle.model.internal.core.ModelActionRole;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.core.ModelReference;
 
 import java.util.Collection;
+import java.util.List;
 
-public class MutatorRuleBinder<T> extends RuleBinder {
-
-    private ModelBinding<T> subjectBinding;
-    private final ModelReference<T> subjectReference;
-    private final ModelActionRole role;
+class MutatorRuleBinder<T> extends RuleBinder {
+    private ModelBinding subjectBinding;
     private final ModelAction<T> action;
 
-    public MutatorRuleBinder(ModelReference<T> subjectReference, ModelActionRole role, ModelAction<T> action, ModelPath scope, Collection<RuleBinder> binders) {
-        super(action.getInputs(), action.getDescriptor(), scope, binders);
-        this.subjectReference = subjectReference;
-        this.role = role;
+    public MutatorRuleBinder(final BindingPredicate subjectReference, List<BindingPredicate> inputs, ModelAction<T> action, Collection<RuleBinder> binders) {
+        super(subjectReference, inputs, action.getDescriptor(), binders);
+        subjectBinding = binding(subjectReference, true, new Action<ModelBinding>() {
+            @Override
+            public void execute(ModelBinding modelBinding) {
+                ModelNodeInternal node = modelBinding.getNode();
+                BindingPredicate predicate = modelBinding.getPredicate();
+                if (predicate.getState() != null && node.getState().compareTo(predicate.getState()) >= 0) {
+                    throw new IllegalStateException(String.format("Cannot add rule %s for model element '%s' at state %s as this element is already at state %s.",
+                        modelBinding.referrer,
+                        node.getPath(),
+                        predicate.getState().previous(),
+                        node.getState()
+                    ));
+                }
+                maybeFire();
+            }
+        });
         this.action = action;
     }
 
-    public ModelActionRole getRole() {
-        return role;
-    }
-
     public ModelAction<T> getAction() {
         return action;
     }
 
-    public ModelReference<T> getSubjectReference() {
-        return subjectReference;
-    }
-
-    public ModelBinding<T> getSubjectBinding() {
+    public ModelBinding getSubjectBinding() {
         return subjectBinding;
     }
 
-    public void bindSubject(ModelNodeInternal modelNode) {
-        assert this.subjectBinding == null;
-        this.subjectBinding = RuleBinder.bind(subjectReference, modelNode);
-        maybeFire();
-    }
-
     @Override
     public boolean isBound() {
-        return subjectBinding != null && super.isBound();
+        return subjectBinding != null && subjectBinding.isBound() && super.isBound();
     }
-
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/NodeAtState.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/NodeAtState.java
new file mode 100644
index 0000000..0144d48
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/NodeAtState.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+import org.gradle.model.internal.core.ModelNode;
+import org.gradle.model.internal.core.ModelPath;
+
+class NodeAtState implements Comparable<NodeAtState> {
+    public final ModelPath path;
+    public final ModelNode.State state;
+
+    public NodeAtState(ModelPath path, ModelNode.State state) {
+        this.path = path;
+        this.state = state;
+    }
+
+    @Override
+    public String toString() {
+        return "node " + path + " at state " + state;
+    }
+
+    @Override
+    public int compareTo(NodeAtState other) {
+        int diff = path.compareTo(other.path);
+        if (diff != 0) {
+            return diff;
+        }
+        return state.compareTo(other.state);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        NodeAtState other = (NodeAtState) obj;
+        return path.equals(other.path) && state.equals(other.state);
+    }
+
+    @Override
+    public int hashCode() {
+        return path.hashCode() ^ state.hashCode();
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/OneOfTypeBinderCreationListener.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/OneOfTypeBinderCreationListener.java
index 0f8e31c..a247511 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/OneOfTypeBinderCreationListener.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/OneOfTypeBinderCreationListener.java
@@ -17,56 +17,30 @@
 package org.gradle.model.internal.registry;
 
 import org.gradle.api.Action;
-import org.gradle.api.Nullable;
 import org.gradle.model.InvalidModelRuleException;
 import org.gradle.model.ModelRuleBindingException;
 import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.core.ModelReference;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.report.AmbiguousBindingReporter;
-import org.gradle.model.internal.type.ModelType;
 
-class OneOfTypeBinderCreationListener extends BinderCreationListener {
-    private final Action<? super ModelNodeInternal> bindAction;
-    private ModelPath boundTo;
-    private ModelRuleDescriptor boundToCreator;
-    private final ModelPath scope;
+class OneOfTypeBinderCreationListener extends ModelBinding {
+    private final Action<ModelBinding> bindAction;
 
-    public OneOfTypeBinderCreationListener(ModelRuleDescriptor descriptor, ModelReference<?> reference, ModelPath scope, boolean writable, Action<? super ModelNodeInternal> bindAction) {
-        super(descriptor, reference, writable);
+    public OneOfTypeBinderCreationListener(ModelRuleDescriptor descriptor, BindingPredicate predicate, boolean writable, Action<ModelBinding> bindAction) {
+        super(descriptor, predicate, writable);
         this.bindAction = bindAction;
-        this.scope = scope;
     }
 
-    @Nullable
-    @Override
-    public ModelPath matchParent() {
-        return null;
-    }
-
-    @Override
-    public ModelPath matchScope() {
-        return scope;
-    }
-
-    @Nullable
-    @Override
-    public ModelType<?> matchType() {
-        return reference.getType();
-    }
-
-    public boolean onCreate(ModelNodeInternal node) {
+    public void onCreate(ModelNodeInternal node) {
         ModelRuleDescriptor creatorDescriptor = node.getDescriptor();
         ModelPath path = node.getPath();
         if (boundTo != null) {
-            throw new InvalidModelRuleException(descriptor, new ModelRuleBindingException(
-                    new AmbiguousBindingReporter(reference, boundTo, boundToCreator, path, creatorDescriptor).asString()
+            throw new InvalidModelRuleException(referrer, new ModelRuleBindingException(
+                    new AmbiguousBindingReporter(predicate.getReference(), boundTo.getPath(), boundTo.getDescriptor(), path, creatorDescriptor).asString()
             ));
         } else {
-            bindAction.execute(node);
-            boundTo = path;
-            boundToCreator = creatorDescriptor;
-            return false; // don't unregister listener, need to keep listening for other potential bindings
+            boundTo = node;
+            bindAction.execute(this);
         }
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/PathBinderCreationListener.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/PathBinderCreationListener.java
index 5b57ffd..fb11839 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/PathBinderCreationListener.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/PathBinderCreationListener.java
@@ -17,40 +17,31 @@
 package org.gradle.model.internal.registry;
 
 import org.gradle.api.Action;
-import org.gradle.api.Nullable;
 import org.gradle.model.InvalidModelRuleException;
 import org.gradle.model.ModelRuleBindingException;
-import org.gradle.model.internal.core.ModelPath;
 import org.gradle.model.internal.core.ModelPromise;
-import org.gradle.model.internal.core.ModelReference;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.report.IncompatibleTypeReferenceReporter;
 
-class PathBinderCreationListener extends BinderCreationListener {
-    private final Action<? super ModelNodeInternal> bindAction;
-    private final ModelPath path;
+class PathBinderCreationListener extends ModelBinding {
+    private final Action<ModelBinding> bindAction;
 
-    public PathBinderCreationListener(ModelRuleDescriptor descriptor, ModelReference<?> reference, ModelPath scope, boolean writable, Action<? super ModelNodeInternal> bindAction) {
-        super(descriptor, reference, writable);
+    public PathBinderCreationListener(ModelRuleDescriptor descriptor, BindingPredicate predicate, boolean writable, Action<ModelBinding> bindAction) {
+        super(descriptor, predicate, writable);
         this.bindAction = bindAction;
-        this.path = scope.descendant(reference.getPath());
     }
 
-    @Nullable
-    @Override
-    public ModelPath matchPath() {
-        return path;
-    }
-
-    public boolean onCreate(ModelNodeInternal node) {
-        ModelRuleDescriptor creatorDescriptor = node.getDescriptor();
+    public void onCreate(ModelNodeInternal node) {
+        if (boundTo != null) {
+            throw new IllegalStateException(String.format("Reference %s for %s is already bound to %s.", predicate.getReference(), referrer, boundTo));
+        }
         ModelPromise promise = node.getPromise();
         if (isTypeCompatible(promise)) {
-            bindAction.execute(node);
-            return true; // bound by type and path, stop listening
+            boundTo = node;
+            bindAction.execute(this);
         } else {
-            throw new InvalidModelRuleException(descriptor, new ModelRuleBindingException(
-                    IncompatibleTypeReferenceReporter.of(creatorDescriptor, promise, reference, writable).asString()
+            throw new InvalidModelRuleException(referrer, new ModelRuleBindingException(
+                IncompatibleTypeReferenceReporter.of(node, promise, predicate.getReference(), writable).asString()
             ));
         }
     }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java
index 5cceb47..26c205c 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java
@@ -17,60 +17,90 @@
 package org.gradle.model.internal.registry;
 
 import net.jcip.annotations.NotThreadSafe;
-import org.gradle.model.internal.core.ModelPath;
-import org.gradle.model.internal.core.ModelReference;
+import org.gradle.api.Action;
+import org.gradle.api.Nullable;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 @NotThreadSafe
-public abstract class RuleBinder {
+abstract class RuleBinder {
 
     private final ModelRuleDescriptor descriptor;
-    private final List<? extends ModelReference<?>> inputReferences;
-    private final ModelPath scope;
+    private final BindingPredicate subjectReference;
+    private final List<BindingPredicate> inputReferences;
     private final Collection<RuleBinder> binders;
 
-    private boolean bindingInputs;
     private int inputsBound;
-    private List<ModelBinding<?>> inputBindings;
+    private List<ModelBinding> inputBindings;
+    private Action<ModelBinding> inputBindAction;
 
-    public RuleBinder(List<? extends ModelReference<?>> inputReferences, ModelRuleDescriptor descriptor, ModelPath scope, Collection<RuleBinder> binders) {
+    public RuleBinder(BindingPredicate subjectReference, List<BindingPredicate> inputReferences, ModelRuleDescriptor descriptor, Collection<RuleBinder> binders) {
+        this.subjectReference = subjectReference;
         this.inputReferences = inputReferences;
         this.descriptor = descriptor;
-        this.scope = scope;
         this.binders = binders;
-        this.inputBindings = inputReferences.isEmpty() ? Collections.<ModelBinding<?>>emptyList() : Arrays.asList(new ModelBinding<?>[inputReferences.size()]); // fix size
+        inputBindAction = new Action<ModelBinding>() {
+            @Override
+            public void execute(ModelBinding modelBinding) {
+                ModelNodeInternal node = modelBinding.getNode();
+                BindingPredicate reference = modelBinding.getPredicate();
+                if (reference.getState() != null && node.getState().compareTo(reference.getState()) > 0) {
+                    throw new IllegalStateException(String.format("Cannot add rule %s with input model element '%s' at state %s as this element is already at state %s.",
+                        modelBinding.referrer,
+                        node.getPath(),
+                        reference.getState(),
+                        node.getState()
+                    ));
+                }
+                ++inputsBound;
+                maybeFire();
+            }
+        };
+        this.inputBindings = inputBindings(inputReferences);
         if (!isBound()) {
             binders.add(this);
         }
     }
 
-    // is binding then inputs for this binder in progress?
-    public boolean isBindingInputs() {
-        return bindingInputs;
-    }
-
-    public void setBindingInputs(boolean bindingInputs) {
-        this.bindingInputs = bindingInputs;
+    private List<ModelBinding> inputBindings(List<BindingPredicate> inputReferences) {
+        if (inputReferences.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<ModelBinding> bindings = new ArrayList<ModelBinding>(inputReferences.size());
+        for (BindingPredicate inputReference : inputReferences) {
+            bindings.add(binding(inputReference, false, inputBindAction));
+        }
+        return bindings;
     }
 
-    public List<? extends ModelReference<?>> getInputReferences() {
-        return inputReferences;
+    protected ModelBinding binding(BindingPredicate reference, boolean writable, Action<ModelBinding> bindAction) {
+        if (reference.getPath() != null) {
+            return new PathBinderCreationListener(descriptor, reference, writable, bindAction);
+        }
+        return new OneOfTypeBinderCreationListener(descriptor, reference, writable, bindAction);
     }
 
-    public ModelBinding<?> getSubjectBinding() {
-        return null;
+    /**
+     * Returns the reference to the <em>output</em> of the rule. The state returned from {@link BindingPredicate#getState()} should reflect
+     * the target state, not the input state. Implicitly, a rule accepts as input the subject in the state that is the predecessor of the target state.
+     */
+    public BindingPredicate getSubjectReference() {
+        return subjectReference;
     }
 
-    public ModelReference<?> getSubjectReference() {
+    /**
+     * A rule may have a subject binding, but may not require it. All rules, however, have a subject and hence a subject reference.
+     */
+    @Nullable
+    public ModelBinding getSubjectBinding() {
         return null;
     }
 
-    public List<ModelBinding<?>> getInputBindings() {
+    public List<ModelBinding> getInputBindings() {
         return inputBindings;
     }
 
@@ -78,18 +108,6 @@ public abstract class RuleBinder {
         return descriptor;
     }
 
-    public ModelPath getScope() {
-        return scope;
-    }
-
-    public void bindInput(int i, ModelNodeInternal modelNode) {
-        assert this.inputBindings.get(i) == null;
-        assert inputsBound < inputBindings.size();
-        this.inputBindings.set(i, bind(inputReferences.get(i), modelNode));
-        ++inputsBound;
-        maybeFire();
-    }
-
     protected void maybeFire() {
         if (isBound()) {
             binders.remove(this);
@@ -99,8 +117,4 @@ public abstract class RuleBinder {
     public boolean isBound() {
         return inputsBound == inputReferences.size();
     }
-
-    static <I> ModelBinding<I> bind(ModelReference<I> reference, ModelNodeInternal modelNode) {
-        return ModelBinding.of(reference, modelNode);
-    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBindings.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBindings.java
new file mode 100644
index 0000000..e89875b
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBindings.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.gradle.model.internal.core.ModelNode;
+import org.gradle.model.internal.core.ModelPath;
+
+import java.util.Collection;
+import java.util.Collections;
+
+class RuleBindings {
+    private final ModelGraph modelGraph;
+    private final NodeIndex rulesBySubject;
+    private final NodeIndex rulesByInput;
+    private final Multimap<ModelPath, Reference> pathReferences = ArrayListMultimap.create();
+    private final Multimap<ModelPath, Reference> scopeReferences = ArrayListMultimap.create();
+
+    public RuleBindings(ModelGraph graph) {
+        this.modelGraph = graph;
+        rulesBySubject = new NodeIndex();
+        rulesByInput = new NodeIndex();
+    }
+
+    public void add(ModelNodeInternal node) {
+        Collection<Reference> references = pathReferences.get(node.getPath());
+        for (Reference reference : references) {
+            bound(reference, node);
+        }
+        references = scopeReferences.get(node.getPath());
+        addTypeMatches(node, references);
+        references = scopeReferences.get(node.getPath().getParent());
+        addTypeMatches(node, references);
+    }
+
+    private void addTypeMatches(ModelNodeInternal node, Collection<Reference> references) {
+        for (Reference reference : references) {
+            if (reference.binding.isTypeCompatible(node.getPromise())) {
+                bound(reference, node);
+            }
+        }
+    }
+
+    private void bound(Reference reference, ModelNodeInternal node) {
+        ModelBinding binding = reference.binding;
+        binding.onCreate(node);
+        if (binding.predicate.getState() != null) {
+            reference.index.boundAtState.put(new NodeAtState(node.getPath(), binding.predicate.getState()), reference.owner);
+        } else {
+            reference.index.boundNoState.put(node.getPath(), reference.owner);
+        }
+    }
+
+    public void remove(ModelNodeInternal node) {
+        rulesBySubject.nodeRemoved(node);
+        rulesByInput.nodeRemoved(node);
+    }
+
+    public void add(RuleBinder ruleBinder) {
+        addRule(ruleBinder, rulesBySubject, subject(ruleBinder));
+        for (ModelBinding binding : ruleBinder.getInputBindings()) {
+            addRule(ruleBinder, rulesByInput, binding);
+        }
+    }
+
+    private void addRule(RuleBinder rule, NodeIndex index, ModelBinding binding) {
+        Reference reference = new Reference(rule, index, binding);
+        BindingPredicate predicate = binding.getPredicate();
+        if (predicate.getPath() != null) {
+            if (predicate.getScope() != null) {
+                throw new UnsupportedOperationException("Currently not implemented");
+            }
+            ModelNodeInternal node = modelGraph.find(predicate.getPath());
+            if (node != null) {
+                bound(reference, node);
+            }
+            // Need to continue to watch to deal with node removal
+            pathReferences.put(predicate.getPath(), reference);
+        } else if (predicate.getScope() != null) {
+            for (ModelNodeInternal node : modelGraph.findAllInScope(predicate.getScope())) {
+                if (binding.isTypeCompatible(node.getPromise())) {
+                    bound(reference, node);
+                }
+            }
+            // Need to continue to watch for potential later matches, which will make the binding ambiguous, and node removal
+            scopeReferences.put(predicate.getScope(), reference);
+        } else {
+            throw new UnsupportedOperationException("Currently not implemented");
+        }
+    }
+
+    private ModelBinding subject(RuleBinder ruleBinder) {
+        if (ruleBinder.getSubjectBinding() != null) {
+            return ruleBinder.getSubjectBinding();
+        }
+        // Create a dummy binding. Could probably reorganise things to avoid this
+        return new ModelBinding(ruleBinder.getDescriptor(), ruleBinder.getSubjectReference(), true) {
+            @Override
+            public void onCreate(ModelNodeInternal node) {
+            }
+        };
+    }
+
+    /**
+     * Returns the set of rules with the given target as their subject.
+     */
+    public Collection<RuleBinder> getRulesWithSubject(NodeAtState target) {
+        return rulesBySubject.get(target);
+    }
+
+    /**
+     * Returns the set of rules with the given target with no state as their subject.
+     */
+    public Collection<RuleBinder> getRulesWithSubject(ModelPath target) {
+        return rulesBySubject.get(target);
+    }
+
+    /**
+     * Returns the set of rules with the given input.
+     */
+    public Collection<RuleBinder> getRulesWithInput(NodeAtState input) {
+        return rulesByInput.get(input);
+    }
+
+    private static class Reference {
+        final ModelBinding binding;
+        final NodeIndex index;
+        final RuleBinder owner;
+
+        public Reference(RuleBinder owner, NodeIndex index, ModelBinding binding) {
+            this.owner = owner;
+            this.index = index;
+            this.binding = binding;
+        }
+    }
+
+    private static class NodeIndex {
+        private final Multimap<NodeAtState, RuleBinder> boundAtState = LinkedHashMultimap.create();
+        private final Multimap<ModelPath, RuleBinder> boundNoState = LinkedHashMultimap.create();
+
+        public void nodeRemoved(ModelNodeInternal node) {
+            // This could be more efficient; assume that removal happens much less often than addition
+            for (ModelNode.State state : ModelNode.State.values()) {
+                for (RuleBinder rule : boundAtState.removeAll(new NodeAtState(node.getPath(), state))) {
+                    if (rule.getSubjectBinding() != null) {
+                        rule.getSubjectBinding().onRemove(node);
+                    }
+                    for (ModelBinding binding : rule.getInputBindings()) {
+                        binding.onRemove(node);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Returns rules for given target and no target state.
+         */
+        public Collection<RuleBinder> get(ModelPath path) {
+            Collection<RuleBinder> result = boundNoState.get(path);
+            return result == null ? Collections.<RuleBinder>emptyList() : result;
+        }
+
+        /**
+         * Returns rules for given target at state.
+         */
+        public Collection<RuleBinder> get(NodeAtState nodeAtState) {
+            Collection<RuleBinder> result = boundAtState.get(nodeAtState);
+            return result == null ? Collections.<RuleBinder>emptyList() : result;
+        }
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleContext.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleContext.java
new file mode 100644
index 0000000..5c42325
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleContext.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.Nullable;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
+
+import java.util.Deque;
+
+public class RuleContext {
+
+    private static final ThreadLocal<Deque<ModelRuleDescriptor>> STACK = new ThreadLocal<Deque<ModelRuleDescriptor>>() {
+        @Override
+        protected Deque<ModelRuleDescriptor> initialValue() {
+            return Lists.newLinkedList();
+        }
+    };
+
+    @Nullable
+    public static ModelRuleDescriptor get() {
+        return STACK.get().peek();
+    }
+
+    @Nullable
+    public static ModelRuleDescriptor nest(ModelRuleDescriptor modelRuleDescriptor) {
+        ModelRuleDescriptor parent = get();
+        if (parent == null) {
+            return modelRuleDescriptor;
+        } else {
+            return new NestedModelRuleDescriptor(parent, modelRuleDescriptor);
+        }
+    }
+
+    @Nullable
+    public static ModelRuleDescriptor nest(String modelRuleDescriptor) {
+        return nest(new SimpleModelRuleDescriptor(modelRuleDescriptor));
+    }
+
+    public static void run(ModelRuleDescriptor descriptor, Runnable runnable) {
+        STACK.get().push(descriptor);
+        try {
+            runnable.run();
+        } finally {
+            STACK.get().pop();
+        }
+    }
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/SingleNodeBinding.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/SingleNodeBinding.java
new file mode 100644
index 0000000..1ca90e5
--- /dev/null
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/SingleNodeBinding.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry;
+
+interface SingleNodeBinding {
+}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java
index f4911ea..831ce49 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java
@@ -29,7 +29,7 @@ import java.util.Collection;
 import java.util.List;
 
 @ThreadSafe
-public class UnboundRulesProcessor {
+class UnboundRulesProcessor {
 
     private final Iterable<? extends RuleBinder> binders;
     private final Transformer<? extends Collection<? extends ModelPath>, ? super ModelPath> suggestionsProvider;
@@ -44,22 +44,15 @@ public class UnboundRulesProcessor {
         for (RuleBinder binder : binders) {
             UnboundRule.Builder builder = UnboundRule.descriptor(binder.getDescriptor().toString());
 
-            ModelPath scope = binder.getScope();
-
-            if (binder.getSubjectReference() != null) {
-                ModelBinding<?> binding = binder.getSubjectBinding();
-                ModelReference<?> reference = binder.getSubjectReference();
-                UnboundRuleInput.Builder inputBuilder = toInputBuilder(binding, reference, scope);
-                if (scope != ModelPath.ROOT) {
-                    inputBuilder.scope(scope.toString());
-                }
+            if (binder.getSubjectBinding() != null) {
+                ModelBinding binding = binder.getSubjectBinding();
+                UnboundRuleInput.Builder inputBuilder = toInputBuilder(binding);
                 builder.mutableInput(inputBuilder);
             }
 
-            for (int i = 0; i < binder.getInputReferences().size(); ++i) {
-                ModelBinding<?> binding = binder.getInputBindings().get(i);
-                ModelReference<?> reference = binder.getInputReferences().get(i);
-                builder.immutableInput(toInputBuilder(binding, reference, binder.getScope()));
+            for (int i = 0; i < binder.getInputBindings().size(); ++i) {
+                ModelBinding binding = binder.getInputBindings().get(i);
+                builder.immutableInput(toInputBuilder(binding));
             }
 
             unboundRules.add(builder.build());
@@ -67,18 +60,22 @@ public class UnboundRulesProcessor {
         return unboundRules;
     }
 
-    private UnboundRuleInput.Builder toInputBuilder(ModelBinding<?> binding, ModelReference<?> reference, ModelPath scope) {
+    private UnboundRuleInput.Builder toInputBuilder(ModelBinding binding) {
+        ModelReference<?> reference = binding.getPredicate().getReference();
         UnboundRuleInput.Builder builder = UnboundRuleInput.type(reference.getType());
         ModelPath path;
-        if (binding != null) {
+        if (binding.isBound()) {
             builder.bound();
             path = binding.getNode().getPath();
         } else {
             path = reference.getPath();
             if (path != null) {
-                path = scope.descendant(path);
                 builder.suggestions(CollectionUtils.stringize(suggestionsProvider.transform(path)));
             }
+            ModelPath scope = reference.getScope();
+            if (scope != null && !scope.equals(ModelPath.ROOT)) {
+                builder.scope(scope.toString());
+            }
         }
         if (path != null) {
             builder.path(path);
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java
index 3704abe..25014c3 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java
@@ -20,6 +20,7 @@ import net.jcip.annotations.ThreadSafe;
 import org.gradle.model.internal.core.ModelPath;
 import org.gradle.model.internal.core.ModelPromise;
 import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.MutableModelNode;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 
 import java.io.PrintWriter;
@@ -46,12 +47,13 @@ public class IncompatibleTypeReferenceReporter {
         this.candidateTypes = candidateTypes;
     }
 
-    public static IncompatibleTypeReferenceReporter of(ModelRuleDescriptor creator, ModelPromise promise, ModelReference<?> reference, boolean writable) {
+    public static IncompatibleTypeReferenceReporter of(MutableModelNode node, ModelPromise promise, ModelReference<?> reference, boolean writable) {
         ModelPath path = reference.getPath();
+        ModelRuleDescriptor creatorDescriptor = node.getDescriptor();
         String pathString = path == null ? "«none»" : path.toString();
         return new IncompatibleTypeReferenceReporter(
-                creator.toString(), pathString, reference.getType().toString(), reference.getDescription(), writable,
-                writable ? promise.getWritableTypeDescriptions() : promise.getReadableTypeDescriptions()
+                creatorDescriptor.toString(), pathString, reference.getType().toString(), reference.getDescription(), writable,
+                writable ? promise.getWritableTypeDescriptions(node) : promise.getReadableTypeDescriptions(node)
         );
     }
 
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ClassTypeWrapper.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ClassTypeWrapper.java
index 1742249..2c8c352 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ClassTypeWrapper.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ClassTypeWrapper.java
@@ -31,7 +31,18 @@ class ClassTypeWrapper implements TypeWrapper {
     }
 
     @Override
-    public String getRepresentation() {
-        return unwrap().getName();
+    public String getRepresentation(boolean full) {
+        if (full) {
+            return unwrap().getName();
+        } else {
+            StringBuffer sb = new StringBuffer();
+            Class<?> clazz = unwrap();
+            sb.append(clazz.getSimpleName());
+            for (Class<?> c = clazz.getEnclosingClass(); c != null; c = c.getEnclosingClass()) {
+                sb.insert(0, '.');
+                sb.insert(0, c.getSimpleName());
+            }
+            return sb.toString();
+        }
     }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelType.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelType.java
index f87081d..4ac9fea 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelType.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelType.java
@@ -230,8 +230,12 @@ public abstract class ModelType<T> {
         }
     }
 
+    public String getSimpleName() {
+        return wrapper.getRepresentation(false);
+    }
+
     public String toString() {
-        return wrapper.getRepresentation();
+        return wrapper.getRepresentation(true);
     }
 
     @Override
@@ -317,16 +321,17 @@ public abstract class ModelType<T> {
     private static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
     private static final TypeWrapper[] EMPTY_TYPE_WRAPPER_ARRAY = new TypeWrapper[0];
 
+    @Nullable
     private static TypeWrapper wrap(Type type) {
         if (type == null) {
-            return NullTypeWrapper.INSTANCE;
+            return null;
         } else if (type instanceof Class) {
             return new ClassTypeWrapper((Class<?>) type);
         } else if (type instanceof ParameterizedType) {
             ParameterizedType parameterizedType = (ParameterizedType) type;
             return new ParameterizedTypeWrapper(
                     toWrappers(parameterizedType.getActualTypeArguments()),
-                    wrap(parameterizedType.getRawType()),
+                    (ClassTypeWrapper) wrap(parameterizedType.getRawType()),
                     wrap(parameterizedType.getOwnerType()),
                     type.hashCode()
             );
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelTypes.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelTypes.java
index 2b57c30..a5336df 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelTypes.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ModelTypes.java
@@ -16,14 +16,38 @@
 
 package org.gradle.model.internal.type;
 
-import java.util.Collection;
+import org.gradle.model.ModelMap;
+import org.gradle.model.ModelSet;
+import org.gradle.model.collection.ManagedSet;
 
 public abstract class ModelTypes {
 
-    public static <T> ModelType<Collection<T>> collectionOf(Class<T> type) {
-        return new ModelType.Builder<Collection<T>>() {
-        }.where(new ModelType.Parameter<T>() {
-        }, ModelType.of(type)).build();
+    public static <I> ModelType<ModelMap<I>> modelMap(Class<I> type) {
+        return modelMap(ModelType.of(type));
+    }
+
+    public static <I> ModelType<ModelMap<I>> modelMap(ModelType<I> type) {
+        return new ModelType.Builder<ModelMap<I>>() {
+        }.where(
+            new ModelType.Parameter<I>() {
+            }, type
+        ).build();
+    }
+
+    public static <I> ModelType<ModelSet<I>> modelSet(ModelType<I> type) {
+        return new ModelType.Builder<ModelSet<I>>() {
+        }.where(
+            new ModelType.Parameter<I>() {
+            }, type
+        ).build();
+    }
+
+    public static <I> ModelType<ManagedSet<I>> managedSet(ModelType<I> type) {
+        return new ModelType.Builder<ManagedSet<I>>() {
+        }.where(
+            new ModelType.Parameter<I>() {
+            }, type
+        ).build();
     }
 
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/NullTypeWrapper.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/NullTypeWrapper.java
deleted file mode 100644
index 6f41a56..0000000
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/NullTypeWrapper.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.model.internal.type;
-
-import java.lang.reflect.Type;
-
-class NullTypeWrapper implements TypeWrapper {
-    final static TypeWrapper INSTANCE = new NullTypeWrapper();
-
-    @Override
-    public Type unwrap() {
-        return null;
-    }
-
-    @Override
-    public String getRepresentation() {
-        return "null";
-    }
-}
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ParameterizedTypeWrapper.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ParameterizedTypeWrapper.java
index c70ec2a..3058745 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ParameterizedTypeWrapper.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/ParameterizedTypeWrapper.java
@@ -16,6 +16,8 @@
 
 package org.gradle.model.internal.type;
 
+import org.gradle.api.Nullable;
+
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Arrays;
@@ -23,11 +25,11 @@ import java.util.Arrays;
 class ParameterizedTypeWrapper implements ParameterizedType, TypeWrapper {
 
     private final TypeWrapper[] actualTypeArguments;
-    private final TypeWrapper rawType;
+    private final ClassTypeWrapper rawType;
     private final TypeWrapper ownerType;
     private final int hashCode;
 
-    public ParameterizedTypeWrapper(TypeWrapper[] actualTypeArguments, TypeWrapper rawType, TypeWrapper ownerType, int hashCode) {
+    public ParameterizedTypeWrapper(TypeWrapper[] actualTypeArguments, ClassTypeWrapper rawType, @Nullable TypeWrapper ownerType, int hashCode) {
         this.actualTypeArguments = actualTypeArguments;
         this.rawType = rawType;
         this.ownerType = ownerType;
@@ -46,7 +48,7 @@ class ParameterizedTypeWrapper implements ParameterizedType, TypeWrapper {
 
     @Override
     public Type getOwnerType() {
-        return ownerType.unwrap();
+        return ownerType == null ? null : ownerType.unwrap();
     }
 
     @Override
@@ -81,45 +83,27 @@ class ParameterizedTypeWrapper implements ParameterizedType, TypeWrapper {
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder();
+        return getRepresentation(true);
+    }
 
-        Type ownerType = getOwnerType();
-        Class<?> rawType = (Class<?>) getRawType();
+    @Override
+    public String getRepresentation(boolean full) {
+        StringBuilder sb = new StringBuilder();
         if (ownerType != null) {
-            if (ownerType instanceof Class) {
-                sb.append(((Class) ownerType).getName());
-            } else {
-                sb.append(ownerType.toString());
-            }
-
-            sb.append(".");
-
-            if (ownerType instanceof ParameterizedTypeWrapper) {
-                // Find simple name of nested type by removing the
-                // shared prefix with owner.
-                Class<?> ownerRaw = (Class<?>) ((ParameterizedTypeWrapper) ownerType).rawType.unwrap();
-                sb.append(rawType.getName().replace(ownerRaw.getName() + "$",
-                        ""));
-            } else {
-                sb.append(rawType.getName());
-            }
+            sb.append(ownerType.getRepresentation(full));
+            sb.append('.');
+            sb.append(rawType.unwrap().getSimpleName());
         } else {
-            sb.append(rawType.getName());
+            sb.append(rawType.getRepresentation(full));
         }
-
-        Type[] actualTypeArguments = getActualTypeArguments();
         if (actualTypeArguments != null && actualTypeArguments.length > 0) {
             sb.append("<");
             boolean first = true;
-            for (Type t : actualTypeArguments) {
+            for (TypeWrapper t : actualTypeArguments) {
                 if (!first) {
                     sb.append(", ");
                 }
-                if (t instanceof Class) {
-                    sb.append(((Class) t).getName());
-                } else {
-                    sb.append(t.toString());
-                }
+                sb.append(t.getRepresentation(full));
                 first = false;
             }
             sb.append(">");
@@ -127,9 +111,4 @@ class ParameterizedTypeWrapper implements ParameterizedType, TypeWrapper {
 
         return sb.toString();
     }
-
-    @Override
-    public String getRepresentation() {
-        return toString();
-    }
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/TypeWrapper.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/TypeWrapper.java
index 64a4847..f722f67 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/TypeWrapper.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/TypeWrapper.java
@@ -21,5 +21,5 @@ import java.lang.reflect.Type;
 interface TypeWrapper {
     Type unwrap();
 
-    String getRepresentation();
+    String getRepresentation(boolean full);
 }
diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/WildcardTypeWrapper.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/WildcardTypeWrapper.java
index 4cebd25..8ab8764 100644
--- a/subprojects/model-core/src/main/java/org/gradle/model/internal/type/WildcardTypeWrapper.java
+++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/type/WildcardTypeWrapper.java
@@ -64,8 +64,13 @@ class WildcardTypeWrapper implements WildcardType, TypeWrapper {
 
     @Override
     public String toString() {
-        Type[] lowerBounds = getLowerBounds();
-        Type[] bounds = lowerBounds;
+        return getRepresentation(true);
+    }
+
+    @Override
+    public String getRepresentation(boolean full) {
+
+        TypeWrapper[] bounds = lowerBounds;
         StringBuilder sb = new StringBuilder();
 
         if (lowerBounds.length > 0) {
@@ -73,7 +78,7 @@ class WildcardTypeWrapper implements WildcardType, TypeWrapper {
         } else {
             Type[] upperBounds = getUpperBounds();
             if (upperBounds.length > 0 && !upperBounds[0].equals(Object.class)) {
-                bounds = upperBounds;
+                bounds = this.upperBounds;
                 sb.append("? extends ");
             } else {
                 return "?";
@@ -83,23 +88,15 @@ class WildcardTypeWrapper implements WildcardType, TypeWrapper {
         assert bounds.length > 0;
 
         boolean first = true;
-        for (Type bound : bounds) {
+        for (TypeWrapper bound : bounds) {
             if (!first) {
                 sb.append(" & ");
             }
 
             first = false;
-            if (bound instanceof Class) {
-                sb.append(((Class) bound).getName());
-            } else {
-                sb.append(bound.toString());
-            }
+            sb.append(bound.getRepresentation(full));
         }
-        return sb.toString();
-    }
 
-    @Override
-    public String getRepresentation() {
-        return toString();
+        return sb.toString();
     }
 }
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedModelMapTypesTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedModelMapTypesTest.groovy
new file mode 100644
index 0000000..ac14967
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedModelMapTypesTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Named
+import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
+import org.gradle.model.internal.manage.schema.extract.InvalidManagedModelElementTypeException
+import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
+import spock.lang.Specification
+
+class ManagedModelMapTypesTest extends Specification {
+
+    def schemaStore = DefaultModelSchemaStore.instance
+
+    @Managed
+    abstract static class ManagedThing {}
+
+    def "type doesn't need to implement named"() {
+        when:
+        schemaStore.getSchema(ModelTypes.modelMap(ManagedThing))
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "type must be managed struct"() {
+        when:
+        schemaStore.getSchema(ModelTypes.modelMap(String))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message == "Invalid managed model type $ModelMap.name<$String.name>: cannot create a model map of type $String.name as it is not a $Managed.name type."
+    }
+
+    def "must have type param"() {
+        when:
+        schemaStore.getSchema(ModelType.of(ModelMap))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message == "Invalid managed model type $ModelMap.name: type parameter of $ModelMap.name has to be specified."
+    }
+
+    @Managed
+    abstract static class WildModelMap {
+        abstract ModelMap<?> getMap()
+    }
+
+    def "must have concrete param"() {
+        when:
+        schemaStore.getSchema(ModelType.of(WildModelMap))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message.startsWith "Invalid managed model type $ModelMap.name<?>: type parameter of $ModelMap.name cannot be a wildcard."
+    }
+
+    def "cannot have map of map"() {
+        when:
+        schemaStore.getSchema(ModelTypes.modelMap(ModelTypes.modelMap(NamedThingInterface)))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message.endsWith "org.gradle.model.ModelMap cannot be used as type parameter of org.gradle.model.ModelMap."
+    }
+
+    @Managed
+    abstract static class MutableName implements Named {
+        abstract void setName(String name)
+    }
+
+    def "element cannot have setName"() {
+        when:
+        schemaStore.getSchema(ModelTypes.modelMap(MutableName))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message.startsWith "Invalid managed model type $MutableName.name: @Managed types implementing $Named.name must not declare a setter for the name property"
+    }
+
+    @Managed
+    static abstract class WritableMapProperty {
+        abstract void setMap(ModelMap<NamedThingInterface> map)
+
+        abstract ModelMap<NamedThingInterface> getMap()
+    }
+
+    def "map cannot be writable"() {
+        when:
+        schemaStore.getSchema(ModelType.of(WritableMapProperty))
+
+        then:
+        def e = thrown InvalidManagedModelElementTypeException
+        e.message == "Invalid managed model type $WritableMapProperty.name: property 'map' cannot have a setter ($ModelMap.name properties must be read only)."
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNamedTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNamedTest.groovy
new file mode 100644
index 0000000..94254cf
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNamedTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Named
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.model.internal.inspect.DefaultModelCreatorFactory
+import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
+import org.gradle.model.internal.manage.schema.extract.InvalidManagedModelElementTypeException
+import spock.lang.Specification
+
+class ManagedNamedTest extends Specification {
+
+    def r = new ModelRegistryHelper()
+    def schemaStore = DefaultModelSchemaStore.getInstance()
+    def creatorFactory = new DefaultModelCreatorFactory(schemaStore)
+
+
+    def "named struct has name name property populated"() {
+        when:
+        r.create(creatorFactory.creator(r.desc("foo"), r.path("foo"), schemaStore.getSchema(NamedThingInterface)))
+
+        then:
+        r.realize("foo", NamedThingInterface).name == "foo"
+
+        when:
+        r.create(creatorFactory.creator(r.desc("bar"), r.path("bar"), schemaStore.getSchema(NamedThingInterface)))
+
+        then:
+        r.realize("bar", NamedThingInterface).name == "bar"
+    }
+
+    @Managed
+    static abstract class NonNamedThing {
+        abstract String getName()
+        abstract void setName(String name)
+    }
+
+    def "named struct does not have name populated if does not implement named"() {
+        when:
+        r.create(creatorFactory.creator(r.desc("foo"), r.path("foo"), schemaStore.getSchema(NonNamedThing)))
+
+        then:
+        r.realize("foo", NonNamedThing).name == null
+    }
+
+    @Managed
+    static abstract class NonNamedThingNoSetter {
+        abstract String getName()
+    }
+
+    def "name requires setter if not named"() {
+        when:
+        schemaStore.getSchema(NonNamedThingNoSetter)
+
+        then:
+        thrown InvalidManagedModelElementTypeException
+    }
+
+    @Managed
+    static abstract class NamedThingWithSetter implements Named {
+        abstract String getName()
+        abstract void setName(String name)
+    }
+
+    def "named cannot have setter"() {
+        when:
+        schemaStore.getSchema(NamedThingWithSetter)
+
+        then:
+        thrown InvalidManagedModelElementTypeException
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNodeBackedModelMapTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNodeBackedModelMapTest.groovy
new file mode 100644
index 0000000..5455939
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/ManagedNodeBackedModelMapTest.groovy
@@ -0,0 +1,860 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Named
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.model.internal.core.ModelNode
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.core.ModelRuleExecutionException
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.model.internal.inspect.DefaultModelCreatorFactory
+import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
+import org.gradle.model.internal.manage.schema.extract.InvalidManagedModelElementTypeException
+import org.gradle.model.internal.registry.UnboundModelRulesException
+import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.normaliseLineSeparators
+
+// TODO - extract out a common fixture for model map “impls”
+// This guy shares some duplication with UnmanagedNodeBackedModelMapTest
+class ManagedNodeBackedModelMapTest extends Specification {
+
+    def path = ModelPath.path("map")
+    def registry = new ModelRegistryHelper()
+    def itemType = ModelType.of(NamedThingInterface)
+    def modelMapType = ModelTypes.modelMap(itemType)
+    def schemaStore = DefaultModelSchemaStore.instance
+    def modelCreatorFactory = new DefaultModelCreatorFactory(schemaStore)
+
+    def setup() {
+        registry.create(modelCreatorFactory.creator(new SimpleModelRuleDescriptor("creator"), path, schemaStore.getSchema(modelMapType)))
+    }
+
+    void mutate(@DelegatesTo(ModelMap) Closure<?> action) {
+        registry.mutate(ModelReference.of(path, modelMapType), ClosureBackedAction.of(action))
+    }
+
+    void realize() {
+        registry.realizeNode(path)
+    }
+
+    void selfClose() {
+        registry.atState(path, ModelNode.State.SelfClosed)
+    }
+
+    def "can define an item with name"() {
+        when:
+        mutate { create("foo") }
+        realize()
+
+        then:
+        realizeChild("foo").name == "foo"
+    }
+
+    private NamedThingInterface realizeChild(String name) {
+        registry.realize(path.child(name), ModelType.of(NamedThingInterface))
+    }
+
+    def "does not eagerly create item"() {
+        when:
+        mutate {
+            create("foo")
+            create("bar")
+        }
+        selfClose()
+
+        then:
+        registry.state(path.child("foo")) == ModelNode.State.Known
+
+        when:
+        realize()
+
+        then:
+        registry.state(path.child("foo")) == ModelNode.State.GraphClosed
+    }
+
+    def "can define item with custom type"() {
+        when:
+        mutate { create("foo", SpecialNamedThingInterface) }
+        realize()
+
+        then:
+        realizeChild("foo") instanceof SpecialNamedThingInterface
+    }
+
+    def "can define item using filtered collection"() {
+        when:
+        mutate {
+            withType(SpecialNamedThingInterface).create("foo")
+            withType(NamedThingInterface).create("bar")
+        }
+        realize()
+
+        then:
+        realizeChild("foo") instanceof SpecialNamedThingInterface
+        realizeChild("bar") instanceof NamedThingInterface
+    }
+
+    def "fails when using filtered collection to define item of type that is not assignable to collection item type"() {
+        when:
+        mutate {
+            withType(String).create("foo")
+        }
+        realize()
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof IllegalArgumentException
+        e.cause.message == "Cannot create an item of type java.lang.String as this is not a subtype of $NamedThingInterface.name."
+    }
+
+    def "can register config rules for item"() {
+        when:
+        mutate {
+            create("foo") {
+                other = "changed"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register config rule and type for item"() {
+        when:
+        mutate {
+            create("foo", SpecialNamedThingInterface) {
+                other = "changed"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can query collection size"() {
+        when:
+        mutate {
+            create("a")
+            create("b")
+        }
+
+        then:
+        realizeAsModelMap().size() == 2
+        !realizeAsModelMap().isEmpty()
+    }
+
+    private ModelMap<NamedThingInterface> realizeAsModelMap() {
+        registry.realize(path, modelMapType)
+    }
+
+    def "can query filtered collection size"() {
+        when:
+        mutate {
+            create("a")
+            create("b", SpecialNamedThingInterface)
+        }
+
+        then:
+        with(realizeAsModelMap()) {
+            assert withType(SpecialNamedThingInterface).size() == 1
+            assert withType(Special).size() == 1
+            assert withType(NamedThingInterface).size() == 2
+            assert withType(String).size() == 0
+
+            assert !withType(SpecialNamedThingInterface).isEmpty()
+            assert withType(String).isEmpty()
+        }
+    }
+
+    def "can query collection membership"() {
+        when:
+        mutate {
+            create("a")
+            create("b")
+        }
+
+        then:
+        realizeAsModelMap().containsKey("a")
+        realizeAsModelMap().containsKey("b")
+        !realizeAsModelMap().containsKey("c")
+    }
+
+    def "can query filtered collection membership"() {
+        when:
+        mutate {
+            create("a")
+            create("b", SpecialNamedThingInterface)
+        }
+
+        then:
+        with(realizeAsModelMap()) {
+            withType(SpecialNamedThingInterface).containsKey("b")
+            withType(Object).containsKey("a")
+            withType(NamedThingInterface).containsKey("a")
+            !withType(SpecialNamedThingInterface).containsKey("a")
+            !withType(Special).containsKey("a")
+            !withType(String).containsKey("a")
+
+            withType(Object).containsKey("b")
+            withType(NamedThingInterface).containsKey("b")
+            withType(SpecialNamedThingInterface).containsKey("b")
+            withType(Special).containsKey("b")
+            !withType(String).containsKey("b")
+        }
+    }
+
+    def "can query collection keys"() {
+        when:
+        mutate {
+            create("a")
+            create("b")
+        }
+
+        then:
+        realizeAsModelMap().keySet() as List == ["a", "b"]
+    }
+
+    def "can access values"() {
+        when:
+        mutate {
+            create("a") { other = "first" }
+            create("b") { other = "second" }
+        }
+
+        then:
+        realizeAsModelMap().values()*.other as Set == ["first", "second"] as Set
+    }
+
+    def "can query filtered collection keys"() {
+        when:
+        mutate {
+            create("b", SpecialNamedThingInterface)
+            create("a")
+        }
+
+        then:
+        with(realizeAsModelMap()) {
+            assert withType(Special).keySet() as List == ["b"]
+            assert withType(NamedThingInterface).keySet() as List == ["a", "b"]
+            assert withType(SpecialNamedThingInterface).keySet() as List == ["b"]
+            assert withType(Special).keySet() as List == ["b"]
+            assert withType(String).keySet().isEmpty()
+        }
+    }
+
+    def "can register mutate rule for item with name"() {
+        when:
+        mutate {
+            named("foo") {
+                assert other == "original"
+                other = "changed"
+            }
+            create("foo") {
+                other = "original"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register mutate rule for item with name using filtered container"() {
+        when:
+        mutate {
+            withType(Object).named("foo") {
+                other += " Object"
+            }
+            withType(Special).named("foo") {
+                other += " Special"
+            }
+            withType(SpecialNamedThingInterface).named("foo") {
+                other += " SpecialNamedThing"
+            }
+            create("foo", SpecialNamedThingInterface) {
+                other = "types:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "types: Object Special SpecialNamedThing"
+    }
+
+    def "fails when named item does not have view with appropriate type"() {
+        when:
+        mutate {
+            withType(String).named("foo") {
+            }
+            create("foo")
+        }
+        realize()
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof InvalidModelRuleException
+        e.cause.cause instanceof ModelRuleBindingException
+        e.cause.cause.message.startsWith("Model reference to element '${path.child('foo')}' with type java.lang.String is invalid due to incompatible types.")
+    }
+
+    static class SetOtherToName extends RuleSource {
+        @Mutate
+        void set(NamedThingInterface thing) {
+            thing.other = thing.name
+        }
+    }
+
+    /**
+     * This test documents the current behaviour, not necessarily the desired.
+     *
+     * Ideally, we'd get a failure here indicating that container item 'foo' is not String & NamedThing
+     */
+    def "rules targeting item of mismatched type are allowed"() {
+        when:
+        mutate {
+            withType(String).named("foo", SetOtherToName)
+            create("foo")
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "foo"
+    }
+
+    def "can register mutate rule for all items using filtered container"() {
+        when:
+        mutate {
+            withType(Named).all {
+                other += " Named"
+            }
+            withType(String).all {
+                other += " String"
+            }
+            withType(NamedThingInterface).all {
+                other += " NamedThing"
+            }
+            withType(Special).all {
+                other += " Special"
+            }
+            withType(SpecialNamedThingInterface).all {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other = "types:"
+            }
+            create("bar", SpecialNamedThingInterface) {
+                other = "types:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "types: Named NamedThing"
+        realizeChild("bar").other == "types: Named NamedThing Special SpecialNamedThing"
+    }
+
+    def "can register mutate rule for all items"() {
+        when:
+        mutate {
+            all {
+                assert other == "original"
+                other = "changed"
+            }
+            create("foo") {
+                other = "original"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register mutate rule for all items with specific type"() {
+        when:
+        mutate {
+            withType(Named) {
+                other += " Named"
+            }
+            withType(String) {
+                other += " String"
+            }
+            withType(Special) {
+                other += " Special"
+            }
+            withType(SpecialNamedThingInterface) {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other = "foo:"
+            }
+            create("bar", SpecialNamedThingInterface) {
+                other = "bar:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "foo: Named"
+        realizeChild("bar").other == "bar: Named Special SpecialNamedThing"
+    }
+
+    def "can register defaults rule for all items"() {
+        when:
+        mutate {
+            all {
+                other += " all{}"
+            }
+            create("foo") {
+                other += " create()"
+            }
+            beforeEach {
+                other = "beforeEach{}"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "beforeEach{} create() all{}"
+    }
+
+    def "can register defaults rule for all items with type"() {
+        when:
+        mutate {
+            beforeEach(Named) {
+                other = "Named"
+            }
+            beforeEach(String) {
+                other += " String"
+            }
+            beforeEach(Special) {
+                other += " Special"
+            }
+            beforeEach(SpecialNamedThingInterface) {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other += " create(foo)"
+            }
+            create("bar", SpecialNamedThingInterface) {
+                other += " create(bar)"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "Named create(foo)"
+        realizeChild("bar").other == "Named Special SpecialNamedThing create(bar)"
+    }
+
+    def "can register finalize rule for all items"() {
+        when:
+        mutate {
+            all {
+                other += " all{}"
+            }
+            afterEach {
+                other += " afterEach{}"
+            }
+            create("foo") {
+                other = "create()"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "create() all{} afterEach{}"
+    }
+
+    def "provides groovy DSL"() {
+        when:
+        mutate {
+            foo {
+                assert other == "original"
+                other = "changed"
+            }
+            foo(NamedThingInterface) {
+                other = "original"
+            }
+            bar(SpecialNamedThingInterface)
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+        realizeChild("bar") instanceof SpecialNamedThingInterface
+    }
+
+    class MutableValue {
+        String value
+    }
+
+    class Bean {
+        String name
+        String value
+    }
+
+    class SpecialBean extends Bean {
+        String other
+    }
+
+    def "sensible error is thrown when trying to apply a class that does not extend RuleSource as a scoped rule"() {
+        def mmType = ModelTypes.modelMap(MutableValue)
+
+        registry
+            .modelMap("values", MutableValue) { it.registerFactory(MutableValue) { new MutableValue() } }
+            .mutate {
+            it.descriptor("mutating elements").path "values" type mmType action { c ->
+                c.create("element")
+                c.named("element", Object)
+            }
+        }
+
+        when:
+        registry.realize(ModelPath.path("values"), ModelType.UNTYPED)
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause.class == InvalidModelRuleDeclarationException
+        e.cause.message == "Type java.lang.Object is not a valid model rule source: rule source classes must directly extend org.gradle.model.RuleSource"
+    }
+
+    static class ElementRules extends RuleSource {
+        @Mutate
+        void connectElementToInput(Bean element, String input) {
+            element.value = input
+        }
+    }
+
+    def "inputs of a rule from an inner source are not realised if the rule is not required"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+        def events = []
+        registry
+            .create("input", "input") { events << "input created" }
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                events << "collection mutated"
+                c.create("element") { events << "$it.name created" }
+                c.named("element", ElementRules)
+            }
+        }
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
+
+        then:
+        events == ["collection mutated"]
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.GraphClosed)
+
+        then:
+        events == ["collection mutated", "element created", "input created"]
+    }
+
+    def "model rule with by-path dependency on non task related collection element's child that does exist passes validation"() {
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .createInstance("foo", new Bean())
+            .mutate {
+            it.path("foo").type(Bean).action("beans.element.mutable", ModelType.of(MutableValue)) { Bean subject, MutableValue input ->
+                subject.value = input.value
+            }
+        }
+        .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.create("element")
+            }
+        }
+        .mutate {
+            it.path "beans.element" node {
+                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue(value: "bar")))
+            }
+        }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
+    static class ByTypeSubjectBoundToScopeChildRule extends RuleSource {
+        @Mutate
+        void mutateScopeChild(MutableValue value) {
+            value.value = "foo"
+        }
+    }
+
+    def "model rule with by-type dependency on non task related collection element's child that does exist passes validation"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.create("element")
+                c.named("element", ByTypeSubjectBoundToScopeChildRule)
+            }
+        }
+        .mutate {
+            it.path "beans.element" descriptor "element child" node {
+                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue()))
+            }
+        }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "adding an unbound scoped rule for an element that is never created results in an error upon validation if the scope parent has been self closed"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.named("element", ElementRules)
+            }
+        }
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        normaliseLineSeparators(e.message) == """The following model rules are unbound:
+  $ElementRules.name#connectElementToInput($Bean.name, $String.name)
+    Mutable:
+      - <unspecified> ($Bean.name) parameter 1 in scope of 'beans.element\'
+    Immutable:
+      - <unspecified> ($String.name) parameter 2"""
+    }
+
+    static class SetOther extends RuleSource {
+        @Mutate
+        void set(SpecialBean bean, String other) {
+            bean.other = other
+            bean.value = "changed"
+        }
+    }
+
+    def "can add rule source to all items of type"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("b1", Bean)
+                c.create("b2", Bean)
+                c.create("sb1", SpecialBean)
+                c.create("sb2", SpecialBean)
+                c.withType(SpecialBean, SetOther)
+            }
+        }
+
+        expect:
+        registry.node("s").state == ModelNode.State.Known
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.node("s").state == ModelNode.State.Known
+        registry.get("beans.b1", Bean).value != "changed"
+        registry.node("s").state == ModelNode.State.Known
+
+        when:
+        def sb2 = registry.get("beans.sb2", SpecialBean)
+
+        then:
+        sb2.other == "other"
+        registry.node("s").state == ModelNode.State.GraphClosed
+
+        when:
+        def sb1 = registry.get("beans.sb1", SpecialBean)
+
+        then:
+        sb1.other == "other"
+    }
+
+    static class SetProp extends RuleSource {
+        @Mutate
+        void m(@Path("foo") Bean bean) {}
+    }
+
+    def "when targeting by type, paths are interpreted relative to item"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("b1", Bean)
+                c.create("sb1", SpecialBean)
+                c.withType(SpecialBean, SetProp)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+        registry.get("beans.sb1", SpecialBean)
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        e.rules.size() == 1
+        e.rules.first().mutableInputs.first().path == "beans.sb1.foo"
+    }
+
+    static class SetValue extends RuleSource {
+        @Mutate
+        void set(Bean bean) {
+            bean.value = "changed"
+        }
+    }
+
+    def "when targeting by type, can have rule use more general type than target"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("sb1", SpecialBean)
+                c.withType(SpecialBean, SetValue)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.get("beans.sb1", SpecialBean).value == "changed"
+    }
+
+    def "when targeting by type, can have rule use more specific type than target"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("sb1", SpecialBean)
+                c.withType(Bean, SetOther)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.get("beans.sb1", SpecialBean).other == "other"
+    }
+
+    def "cannot read child in mutative method"() {
+        when:
+        mutate {
+            create('foo')
+        }
+        mutate {
+            get('foo')
+        }
+        realizeAsModelMap()
+
+        then:
+        def e = thrown ModelRuleExecutionException
+        e.cause instanceof WriteOnlyModelViewException
+    }
+
+    def "cannot read size in mutative method"() {
+        when:
+        mutate {
+            size()
+        }
+        realizeAsModelMap()
+
+        then:
+        def e = thrown ModelRuleExecutionException
+        e.cause instanceof WriteOnlyModelViewException
+    }
+
+    def "cannot add when realized"() {
+        when:
+        realizeAsModelMap().create("foo")
+
+        then:
+        thrown ModelViewClosedException
+    }
+
+    @Managed
+    abstract static class Invalid<T> implements SpecialNamedThingInterface {
+
+    }
+    def "cannot create invalid subtype"() {
+        when:
+        mutate {
+            create("foo", Invalid)
+        }
+        realizeAsModelMap()
+
+        then:
+        def e = thrown ModelRuleExecutionException
+        e.cause instanceof InvalidManagedModelElementTypeException
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/NamedThingInterface.java b/subprojects/model-core/src/test/groovy/org/gradle/model/NamedThingInterface.java
new file mode 100644
index 0000000..02f4479
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/NamedThingInterface.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Named;
+
+ at Managed
+public interface NamedThingInterface extends Named {
+    String getOther();
+
+    void setOther(String string);
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/Special.java b/subprojects/model-core/src/test/groovy/org/gradle/model/Special.java
new file mode 100644
index 0000000..13ffdb6
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/Special.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.model;
+
+/**
+ * Out on its own due to http://jira.codehaus.org/browse/GROOVY-7010
+ */
+public interface Special {
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/SpecialNamedThingInterface.java b/subprojects/model-core/src/test/groovy/org/gradle/model/SpecialNamedThingInterface.java
new file mode 100644
index 0000000..4ce1ab2
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/SpecialNamedThingInterface.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+ at Managed
+public interface SpecialNamedThingInterface extends NamedThingInterface, Special {
+    String getSpecial();
+
+    void setSpecial(String string);
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/UnmanagedNodeBackedModelMapTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/UnmanagedNodeBackedModelMapTest.groovy
new file mode 100644
index 0000000..8581ecc
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/UnmanagedNodeBackedModelMapTest.groovy
@@ -0,0 +1,872 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Named
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.Namer
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer
+import org.gradle.api.internal.rules.RuleAwareNamedDomainObjectFactoryRegistry
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.model.collection.internal.PolymorphicModelMapProjection
+import org.gradle.model.internal.core.*
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.model.internal.registry.UnboundModelRulesException
+import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.normaliseLineSeparators
+
+class UnmanagedNodeBackedModelMapTest extends Specification {
+
+    def type = new ModelType<NamedThing>() {}
+
+    static class NamedThing implements Named {
+        String name
+        String other
+
+        NamedThing(String name) {
+            this.name = name
+        }
+    }
+
+    static class SpecialNamedThing extends NamedThing implements Special {
+        SpecialNamedThing(String name) {
+            super(name)
+        }
+    }
+
+    static class Container<T> extends DefaultPolymorphicDomainObjectContainer<T> implements RuleAwareNamedDomainObjectFactoryRegistry<T>, NamedEntityInstantiator<T> {
+        Container(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+            super(type, instantiator, namer)
+        }
+
+        @Override
+        <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
+            registerFactory(type, factory)
+        }
+    }
+
+    def containerPath = ModelPath.path("container")
+    def modelMapType = new ModelType<ModelMap<NamedThing>>() {}
+    def registry = new ModelRegistryHelper()
+    def itemType = ModelType.of(NamedThing)
+
+    def setup() {
+        registry.create(
+            ModelCreators.bridgedInstance(
+                ModelReference.of("container", new ModelType<NamedEntityInstantiator<NamedThing>>() {}),
+                { name, type -> DirectInstantiator.instantiate(type, name) } as NamedEntityInstantiator
+            )
+                .descriptor("container")
+                .withProjection(PolymorphicModelMapProjection.of(itemType, NodeBackedModelMap.createUsingParentNode(itemType)))
+                .build()
+        )
+    }
+
+    void mutate(@DelegatesTo(ModelMap) Closure<? super ModelMap<NamedThing>> action) {
+        def mutator = Stub(ModelAction)
+        mutator.subject >> ModelReference.of(containerPath, new ModelType<ModelMap<NamedThing>>() {})
+        mutator.descriptor >> new SimpleModelRuleDescriptor("foo")
+        mutator.execute(*_) >> { new ClosureBackedAction<NamedThing>(action).execute(it[1]) }
+
+        registry.configure(ModelActionRole.Mutate, mutator)
+    }
+
+    void realize() {
+        registry.realizeNode(containerPath)
+    }
+
+    void selfClose() {
+        registry.atState(containerPath, ModelNode.State.SelfClosed)
+    }
+
+    def "can define an item with name"() {
+        when:
+        mutate { create("foo") }
+        realize()
+
+        then:
+        realizeChild("foo").name == "foo"
+    }
+
+    private NamedThing realizeChild(String name) {
+        registry.realize(containerPath.child(name), ModelType.of(NamedThing))
+    }
+
+    def "does not eagerly create item"() {
+        when:
+        mutate {
+            create("foo")
+            create("bar")
+        }
+        selfClose()
+
+        then:
+        registry.state("container.foo") == ModelNode.State.Known
+
+        when:
+        realize()
+
+        then:
+        registry.state("container.foo") == ModelNode.State.GraphClosed
+    }
+
+    def "can define item with custom type"() {
+        when:
+        mutate { create("foo", SpecialNamedThing) }
+        realize()
+
+        then:
+        realizeChild("foo") instanceof SpecialNamedThing
+    }
+
+    def "can define item using filtered collection"() {
+        when:
+        mutate {
+            withType(SpecialNamedThing).create("foo")
+            withType(NamedThing).create("bar")
+        }
+        realize()
+
+        then:
+        realizeChild("foo") instanceof SpecialNamedThing
+        realizeChild("bar") instanceof NamedThing
+    }
+
+    def "fails when using filtered collection to define item of type that is not assignable to collection item type"() {
+        when:
+        mutate {
+            withType(String).create("foo")
+        }
+        realize()
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof IllegalArgumentException
+        e.cause.message == "Cannot create an item of type java.lang.String as this is not a subtype of $NamedThing.name."
+    }
+
+    def "can register config rules for item"() {
+        when:
+        mutate {
+            create("foo") {
+                other = "changed"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register config rule and type for item"() {
+        when:
+        mutate {
+            create("foo", SpecialNamedThing) {
+                other = "changed"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can query collection size"() {
+        when:
+        mutate {
+            assert size() == 0
+            assert it.isEmpty()
+
+            create("a")
+            create("b")
+
+            assert size() == 2
+            assert !isEmpty()
+        }
+
+        then:
+        realizeAsModelMap().size() == 2
+    }
+
+    private ModelMap<NamedThing> realizeAsModelMap() {
+        registry.realize(containerPath, modelMapType)
+    }
+
+    def "can query filtered collection size"() {
+        when:
+        mutate {
+            create("a")
+            create("b", SpecialNamedThing)
+
+            assert withType(SpecialNamedThing).size() == 1
+            assert withType(Special).size() == 1
+            assert withType(NamedThing).size() == 2
+            assert withType(String).size() == 0
+
+            assert !withType(SpecialNamedThing).isEmpty()
+            assert withType(String).isEmpty()
+        }
+
+        then:
+        realizeAsModelMap().withType(SpecialNamedThing).size() == 1
+    }
+
+    def "can query collection membership"() {
+        when:
+        mutate {
+            assert !containsKey("a")
+            assert !containsKey(12)
+
+            create("a")
+            create("b")
+
+            assert it.containsKey("a")
+        }
+
+        then:
+        realizeAsModelMap().containsKey("a")
+    }
+
+    def "can query filtered collection membership"() {
+        when:
+        mutate {
+            assert !withType(NamedThing).containsKey("a")
+            assert !withType(Integer).containsKey(12)
+
+            create("a")
+            create("b", SpecialNamedThing)
+
+            assert withType(Object).containsKey("a")
+            assert withType(NamedThing).containsKey("a")
+            assert !withType(SpecialNamedThing).containsKey("a")
+            assert !withType(Special).containsKey("a")
+            assert !withType(String).containsKey("a")
+
+            assert withType(Object).containsKey("b")
+            assert withType(NamedThing).containsKey("b")
+            assert withType(SpecialNamedThing).containsKey("b")
+            assert withType(Special).containsKey("b")
+            assert !withType(String).containsKey("b")
+        }
+
+        then:
+        realizeAsModelMap().withType(SpecialNamedThing).containsKey("b")
+    }
+
+    def "can query collection keys"() {
+        when:
+        mutate {
+            assert keySet().isEmpty()
+
+            create("a")
+            create("b")
+
+            assert keySet() as List == ["a", "b"]
+        }
+
+        then:
+        realizeAsModelMap().keySet() as List == ["a", "b"]
+    }
+
+    def "can access values"() {
+        when:
+        mutate {
+            create("a") { other = "first" }
+            create("b") { other = "second" }
+        }
+
+        then:
+        realizeAsModelMap().values()*.other as Set == ["first", "second"] as Set
+    }
+
+    def "can query filtered collection keys"() {
+        when:
+        mutate {
+            assert withType(NamedThing).keySet().isEmpty()
+            assert withType(String).keySet().isEmpty()
+
+            create("b", SpecialNamedThing)
+            create("a")
+
+            assert withType(NamedThing).keySet() as List == ["a", "b"]
+            assert withType(SpecialNamedThing).keySet() as List == ["b"]
+            assert withType(Special).keySet() as List == ["b"]
+            assert withType(String).keySet().isEmpty()
+        }
+
+        then:
+        realizeAsModelMap().withType(Special).keySet() as List == ["b"]
+    }
+
+    def "can register mutate rule for item with name"() {
+        when:
+        mutate {
+            named("foo") {
+                assert other == "original"
+                other = "changed"
+            }
+            create("foo") {
+                other = "original"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register mutate rule for item with name using filtered container"() {
+        when:
+        mutate {
+            withType(Object).named("foo") {
+                other += " Object"
+            }
+            withType(Special).named("foo") {
+                other += " Special"
+            }
+            withType(SpecialNamedThing).named("foo") {
+                other += " SpecialNamedThing"
+            }
+            create("foo", SpecialNamedThing) {
+                other = "types:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "types: Object Special SpecialNamedThing"
+    }
+
+    def "fails when named item does not have view with appropriate type"() {
+        when:
+        mutate {
+            withType(String).named("foo") {
+            }
+            create("foo")
+        }
+        realize()
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof InvalidModelRuleException
+        e.cause.cause instanceof ModelRuleBindingException
+        e.cause.cause.message.startsWith("Model reference to element 'container.foo' with type java.lang.String is invalid due to incompatible types.")
+    }
+
+    static class SetOtherToName extends RuleSource {
+        @Mutate
+        void set(NamedThing thing) {
+            thing.other = thing.name
+        }
+    }
+
+    /**
+     * This test documents the current behaviour, not necessarily the desired.
+     *
+     * Ideally, we'd get a failure here indicating that container item 'foo' is not String & NamedThing
+     */
+    def "rules targeting item of mismatched type are allowed"() {
+        when:
+        mutate {
+            withType(String).named("foo", SetOtherToName)
+            create("foo")
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "foo"
+    }
+
+    def "can register mutate rule for all items using filtered container"() {
+        when:
+        mutate {
+            withType(Named).all {
+                other += " Named"
+            }
+            withType(String).all {
+                other += " String"
+            }
+            withType(NamedThing).all {
+                other += " NamedThing"
+            }
+            withType(Special).all {
+                other += " Special"
+            }
+            withType(SpecialNamedThing).all {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other = "types:"
+            }
+            create("bar", SpecialNamedThing) {
+                other = "types:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "types: Named NamedThing"
+        realizeChild("bar").other == "types: Named NamedThing Special SpecialNamedThing"
+    }
+
+    def "can register mutate rule for all items"() {
+        when:
+        mutate {
+            all {
+                assert other == "original"
+                other = "changed"
+            }
+            create("foo") {
+                other = "original"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+    }
+
+    def "can register mutate rule for all items with specific type"() {
+        when:
+        mutate {
+            withType(Named) {
+                other += " Named"
+            }
+            withType(String) {
+                other += " String"
+            }
+            withType(Special) {
+                other += " Special"
+            }
+            withType(SpecialNamedThing) {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other = "foo:"
+            }
+            create("bar", SpecialNamedThing) {
+                other = "bar:"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "foo: Named"
+        realizeChild("bar").other == "bar: Named Special SpecialNamedThing"
+    }
+
+    def "can register defaults rule for all items"() {
+        when:
+        mutate {
+            all {
+                other += " all{}"
+            }
+            create("foo") {
+                other += " create()"
+            }
+            beforeEach {
+                other = "beforeEach{}"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "beforeEach{} create() all{}"
+    }
+
+    def "can register defaults rule for all items with type"() {
+        when:
+        mutate {
+            beforeEach(Named) {
+                other = "Named"
+            }
+            beforeEach(String) {
+                other += " String"
+            }
+            beforeEach(Special) {
+                other += " Special"
+            }
+            beforeEach(SpecialNamedThing) {
+                other += " SpecialNamedThing"
+            }
+            create("foo") {
+                other += " create(foo)"
+            }
+            create("bar", SpecialNamedThing) {
+                other += " create(bar)"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "Named create(foo)"
+        realizeChild("bar").other == "Named Special SpecialNamedThing create(bar)"
+    }
+
+    def "can register finalize rule for all items"() {
+        when:
+        mutate {
+            all {
+                other += " all{}"
+            }
+            afterEach {
+                other += " afterEach{}"
+            }
+            create("foo") {
+                other = "create()"
+            }
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "create() all{} afterEach{}"
+    }
+
+    def "provides groovy DSL"() {
+        when:
+        mutate {
+            foo {
+                assert other == "original"
+                other = "changed"
+            }
+            foo(NamedThing) {
+                other = "original"
+            }
+            bar(SpecialNamedThing)
+        }
+        realize()
+
+        then:
+        realizeChild("foo").other == "changed"
+        realizeChild("bar") instanceof SpecialNamedThing
+    }
+
+    class MutableValue {
+        String value
+    }
+
+    class Bean {
+        String name
+        String value
+    }
+
+    class SpecialBean extends Bean {
+        String other
+    }
+
+    def "sensible error is thrown when trying to apply a class that does not extend RuleSource as a scoped rule"() {
+        def mmType = ModelTypes.modelMap(MutableValue)
+
+        registry
+            .modelMap("values", MutableValue) { it.registerFactory(MutableValue) { new MutableValue() } }
+            .mutate {
+            it.descriptor("mutating elements").path "values" type mmType action { c ->
+                c.create("element")
+                c.named("element", Object)
+            }
+        }
+
+        when:
+        registry.realize(ModelPath.path("values"), ModelType.UNTYPED)
+
+        then:
+        ModelRuleExecutionException e = thrown()
+        e.cause.class == InvalidModelRuleDeclarationException
+        e.cause.message == "Type java.lang.Object is not a valid model rule source: rule source classes must directly extend org.gradle.model.RuleSource"
+    }
+
+    static class ElementRules extends RuleSource {
+        @Mutate
+        void connectElementToInput(Bean element, String input) {
+            element.value = input
+        }
+    }
+
+    def "inputs of a rule from an inner source are not realised if the rule is not required"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+        def events = []
+        registry
+            .create("input", "input") { events << "input created" }
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                events << "collection mutated"
+                c.create("element") { events << "$it.name created" }
+                c.named("element", ElementRules)
+            }
+        }
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
+
+        then:
+        events == ["collection mutated"]
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.GraphClosed)
+
+        then:
+        events == ["collection mutated", "element created", "input created"]
+    }
+
+    def "model rule with by-path dependency on non task related collection element's child that does exist passes validation"() {
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .createInstance("foo", new Bean())
+            .mutate {
+            it.path("foo").type(Bean).action("beans.element.mutable", ModelType.of(MutableValue)) { Bean subject, MutableValue input ->
+                subject.value = input.value
+            }
+        }
+        .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.create("element")
+            }
+        }
+        .mutate {
+            it.path "beans.element" node {
+                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue(value: "bar")))
+            }
+        }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
+    static class ByTypeSubjectBoundToScopeChildRule extends RuleSource {
+        @Mutate
+        void mutateScopeChild(MutableValue value) {
+            value.value = "foo"
+        }
+    }
+
+    def "model rule with by-type dependency on non task related collection element's child that does exist passes validation"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.create("element")
+                c.named("element", ByTypeSubjectBoundToScopeChildRule)
+            }
+        }
+        .mutate {
+            it.path "beans.element" descriptor "element child" node {
+                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue()))
+            }
+        }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "adding an unbound scoped rule for an element that is never created results in an error upon validation if the scope parent has been self closed"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
+            .mutate {
+            it.path "beans" type mmType action { c ->
+                c.named("element", ElementRules)
+            }
+        }
+
+        when:
+        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        normaliseLineSeparators(e.message) == """The following model rules are unbound:
+  $ElementRules.name#connectElementToInput($Bean.name, $String.name)
+    Mutable:
+      - <unspecified> ($Bean.name) parameter 1 in scope of 'beans.element\'
+    Immutable:
+      - <unspecified> ($String.name) parameter 2"""
+    }
+
+    static class SetOther extends RuleSource {
+        @Mutate
+        void set(SpecialBean bean, String other) {
+            bean.other = other
+            bean.value = "changed"
+        }
+    }
+
+    def "can add rule source to all items of type"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("b1", Bean)
+                c.create("b2", Bean)
+                c.create("sb1", SpecialBean)
+                c.create("sb2", SpecialBean)
+                c.withType(SpecialBean, SetOther)
+            }
+        }
+
+        expect:
+        registry.node("s").state == ModelNode.State.Known
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.node("s").state == ModelNode.State.Known
+        registry.get("beans.b1", Bean).value != "changed"
+        registry.node("s").state == ModelNode.State.Known
+
+        when:
+        def sb2 = registry.get("beans.sb2", SpecialBean)
+
+        then:
+        sb2.other == "other"
+        registry.node("s").state == ModelNode.State.GraphClosed
+
+        when:
+        def sb1 = registry.get("beans.sb1", SpecialBean)
+
+        then:
+        sb1.other == "other"
+    }
+
+    static class SetProp extends RuleSource {
+        @Mutate
+        void m(@Path("foo") Bean bean) {}
+    }
+
+    def "when targeting by type, paths are interpreted relative to item"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("b1", Bean)
+                c.create("sb1", SpecialBean)
+                c.withType(SpecialBean, SetProp)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+        registry.get("beans.sb1", SpecialBean)
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        e.rules.size() == 1
+        e.rules.first().mutableInputs.first().path == "beans.sb1.foo"
+    }
+
+    static class SetValue extends RuleSource {
+        @Mutate
+        void set(Bean bean) {
+            bean.value = "changed"
+        }
+    }
+
+    def "when targeting by type, can have rule use more general type than target"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("sb1", SpecialBean)
+                c.withType(SpecialBean, SetValue)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.get("beans.sb1", SpecialBean).value == "changed"
+    }
+
+    def "when targeting by type, can have rule use more specific type than target"() {
+        given:
+        def mmType = ModelTypes.modelMap(Bean)
+
+        registry
+            .modelMap("beans", Bean) {
+            it.registerFactory(Bean) { new Bean(name: it) }
+            it.registerFactory(SpecialBean) { new SpecialBean(name: it) }
+        }
+
+        .createInstance("s", "other")
+            .mutate {
+            it.path("beans").type(mmType).action { c ->
+                c.create("sb1", SpecialBean)
+                c.withType(Bean, SetOther)
+            }
+        }
+
+        when:
+        registry.atState("beans", ModelNode.State.SelfClosed)
+
+        then:
+        registry.get("beans.sb1", SpecialBean).other == "other"
+    }
+
+    def "cannot add when realized"() {
+        when:
+        realizeAsModelMap().create("foo")
+
+        then:
+        thrown ModelViewClosedException
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/CollectionBuilderModelViewTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/CollectionBuilderModelViewTest.groovy
deleted file mode 100644
index 16cb147..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/CollectionBuilderModelViewTest.groovy
+++ /dev/null
@@ -1,51 +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.model.collection.internal
-
-import org.gradle.model.ModelViewClosedException
-import org.gradle.model.collection.CollectionBuilder
-import org.gradle.model.internal.core.CollectionBuilderModelView
-import org.gradle.model.internal.core.ModelPath
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
-import org.gradle.model.internal.type.ModelType
-import spock.lang.Specification
-
-class CollectionBuilderModelViewTest extends Specification {
-
-    def "cannot create items after view is closed"() {
-        def builder = Mock(CollectionBuilder)
-        def view = new CollectionBuilderModelView(ModelPath.path("things"), ModelType.of(CollectionBuilder), builder, new SimpleModelRuleDescriptor("foo"))
-        def instance = view.instance
-
-        when:
-        instance.create("foo")
-
-        then:
-        1 * builder.create("foo")
-
-        when:
-        view.close()
-
-        and:
-        instance.create("foo")
-
-        then:
-        thrown ModelViewClosedException
-
-        // assume other methods are implemented in the same way
-    }
-}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/DefaultCollectionBuilderTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/DefaultCollectionBuilderTest.groovy
deleted file mode 100644
index 70267e7..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/DefaultCollectionBuilderTest.groovy
+++ /dev/null
@@ -1,842 +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.model.collection.internal
-
-import org.gradle.api.Named
-import org.gradle.api.PolymorphicDomainObjectContainer
-import org.gradle.api.internal.ClosureBackedAction
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.model.*
-import org.gradle.model.collection.CollectionBuilder
-import org.gradle.model.internal.core.*
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
-import org.gradle.model.internal.fixture.ModelRegistryHelper
-import org.gradle.model.internal.registry.UnboundModelRulesException
-import org.gradle.model.internal.type.ModelType
-import spock.lang.Specification
-
-import static org.gradle.util.TextUtil.normaliseLineSeparators
-
-class DefaultCollectionBuilderTest extends Specification {
-
-    def type = new ModelType<NamedThing>() {}
-
-    class NamedThing implements Named {
-        String name
-        String other
-    }
-
-    class SpecialNamedThing extends NamedThing implements Special {
-    }
-
-    def containerPath = ModelPath.path("container")
-    def containerType = new ModelType<PolymorphicDomainObjectContainer<NamedThing>>() {}
-    def collectionBuilderType = new ModelType<CollectionBuilder<NamedThing>>() {}
-    def registry = new ModelRegistryHelper()
-    def container = new DefaultPolymorphicDomainObjectContainer<NamedThing>(NamedThing, DirectInstantiator.INSTANCE, { it.getName() })
-
-    def setup() {
-        BridgedCollections.dynamicTypes(registry, containerPath, "container", containerType, containerType, ModelType.of(NamedThing), container, Named.Namer.forType(NamedThing), BridgedCollections.itemDescriptor("container"))
-        container.registerFactory(NamedThing) {
-            NamedThing.newInstance(name: it)
-        }
-        container.registerFactory(SpecialNamedThing) { SpecialNamedThing.newInstance(name: it) }
-    }
-
-    void mutate(@DelegatesTo(CollectionBuilder) Closure<? super CollectionBuilder<NamedThing>> action) {
-        def mutator = Stub(ModelAction)
-        mutator.subject >> ModelReference.of(containerPath, new ModelType<CollectionBuilder<NamedThing>>() {})
-        mutator.descriptor >> new SimpleModelRuleDescriptor("foo")
-        mutator.execute(*_) >> { new ClosureBackedAction<NamedThing>(action).execute(it[1]) }
-
-        registry.configure(ModelActionRole.Mutate, mutator)
-    }
-
-    void realize() {
-        registry.realizeNode(containerPath)
-    }
-
-    def "can define an item with name"() {
-        when:
-        mutate { create("foo") }
-        realize()
-
-        then:
-        container.getByName("foo") != null
-        registry.realize(containerPath.child("foo"), ModelType.of(NamedThing)) == container.getByName("foo")
-    }
-
-    def "does not eagerly create item"() {
-        when:
-        mutate {
-            create("foo")
-            create("bar")
-        }
-
-        then:
-        container.isEmpty()
-
-        when:
-        realize()
-
-        then:
-        container.getByName("bar")
-    }
-
-    def "can define item with custom type"() {
-        when:
-        mutate { create("foo", SpecialNamedThing) }
-        realize()
-
-        then:
-        container.getByName("foo") instanceof SpecialNamedThing
-    }
-
-    def "can define item using filtered collection"() {
-        when:
-        mutate {
-            withType(SpecialNamedThing).create("foo")
-            withType(NamedThing).create("bar")
-        }
-        realize()
-
-        then:
-        container.getByName("foo") instanceof SpecialNamedThing
-        container.getByName("bar").class == NamedThing
-    }
-
-    def "fails when using filtered collection to define item of type that is not assignable to collection item type"() {
-        when:
-        mutate {
-            withType(String).create("foo")
-        }
-        realize()
-
-        then:
-        ModelRuleExecutionException e = thrown()
-        e.cause instanceof IllegalArgumentException
-        e.cause.message == "Cannot create an item of type java.lang.String as this is not a subtype of $NamedThing.name."
-    }
-
-    def "can register config rules for item"() {
-        when:
-        mutate {
-            create("foo") {
-                other = "changed"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "changed"
-    }
-
-    def "can register config rule and type for item"() {
-        when:
-        mutate {
-            create("foo", SpecialNamedThing) {
-                other = "changed"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "changed"
-    }
-
-    def "can query collection size"() {
-        when:
-        mutate {
-            assert size() == 0
-            assert it.isEmpty()
-
-            create("a")
-            create("b")
-
-            assert size() == 2
-            assert !isEmpty()
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).size() == 2
-    }
-
-    def "can query filtered collection size"() {
-        when:
-        mutate {
-            create("a")
-            create("b", SpecialNamedThing)
-
-            assert withType(SpecialNamedThing).size() == 1
-            assert withType(Special).size() == 1
-            assert withType(NamedThing).size() == 2
-            assert withType(String).size() == 0
-
-            assert !withType(SpecialNamedThing).isEmpty()
-            assert withType(String).isEmpty()
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).withType(SpecialNamedThing).size() == 1
-    }
-
-    def "can query collection membership"() {
-        when:
-        mutate {
-            assert !containsKey("a")
-            assert !containsKey(12)
-
-            create("a")
-            create("b")
-
-            assert it.containsKey("a")
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).containsKey("a")
-    }
-
-    def "can query filtered collection membership"() {
-        when:
-        mutate {
-            assert !withType(NamedThing).containsKey("a")
-            assert !withType(Integer).containsKey(12)
-
-            create("a")
-            create("b", SpecialNamedThing)
-
-            assert withType(Object).containsKey("a")
-            assert withType(NamedThing).containsKey("a")
-            assert !withType(SpecialNamedThing).containsKey("a")
-            assert !withType(Special).containsKey("a")
-            assert !withType(String).containsKey("a")
-
-            assert withType(Object).containsKey("b")
-            assert withType(NamedThing).containsKey("b")
-            assert withType(SpecialNamedThing).containsKey("b")
-            assert withType(Special).containsKey("b")
-            assert !withType(String).containsKey("b")
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).withType(SpecialNamedThing).containsKey("b")
-    }
-
-    def "can query collection keys"() {
-        when:
-        mutate {
-            assert keySet().isEmpty()
-
-            create("a")
-            create("b")
-
-            assert keySet() as List == ["a", "b"]
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).keySet() as List == ["a", "b"]
-    }
-
-    def "can query filtered collection keys"() {
-        when:
-        mutate {
-            assert withType(NamedThing).keySet().isEmpty()
-            assert withType(String).keySet().isEmpty()
-
-            create("b", SpecialNamedThing)
-            create("a")
-
-            assert withType(NamedThing).keySet() as List == ["a", "b"]
-            assert withType(SpecialNamedThing).keySet() as List == ["b"]
-            assert withType(Special).keySet() as List == ["b"]
-            assert withType(String).keySet().isEmpty()
-        }
-
-        then:
-        registry.realize(containerPath, collectionBuilderType).withType(Special).keySet() as List == ["b"]
-    }
-
-    def "can register mutate rule for item with name"() {
-        when:
-        mutate {
-            named("foo") {
-                assert other == "original"
-                other = "changed"
-            }
-            create("foo") {
-                other = "original"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "changed"
-    }
-
-    def "can register mutate rule for item with name using filtered container"() {
-        when:
-        mutate {
-            withType(Object).named("foo") {
-                other += " Object"
-            }
-            withType(Special).named("foo") {
-                other += " Special"
-            }
-            withType(SpecialNamedThing).named("foo") {
-                other += " SpecialNamedThing"
-            }
-            create("foo", SpecialNamedThing) {
-                other = "types:"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "types: Object Special SpecialNamedThing"
-    }
-
-    def "fails when named item does not have view with appropriate type"() {
-        when:
-        mutate {
-            withType(String).named("foo") {
-            }
-            create("foo")
-        }
-        realize()
-
-        then:
-        InvalidModelRuleException e = thrown()
-        e.cause instanceof ModelRuleBindingException
-        e.cause.message.startsWith("Model reference to element 'container.foo' with type java.lang.String is invalid due to incompatible types.")
-    }
-
-    static class SetOtherToName extends RuleSource {
-        @Mutate
-        void set(NamedThing thing) {
-            thing.other = thing.name
-        }
-    }
-
-    /**
-     * This test documents the current behaviour, not necessarily the desired.
-     *
-     * Ideally, we'd get a failure here indicating that container item 'foo' is not String & NamedThing
-     */
-    def "rules targeting item of mismatched type are allowed"() {
-        when:
-        mutate {
-            withType(String).named("foo", SetOtherToName)
-            create("foo")
-        }
-        realize()
-
-        then:
-        registry.get(containerPath.child("foo")).other == "foo"
-    }
-
-    def "can register mutate rule for all items using filtered container"() {
-        when:
-        mutate {
-            withType(Named).all {
-                other += " Named"
-            }
-            withType(String).all {
-                other += " String"
-            }
-            withType(NamedThing).all {
-                other += " NamedThing"
-            }
-            withType(Special).all {
-                other += " Special"
-            }
-            withType(SpecialNamedThing).all {
-                other += " SpecialNamedThing"
-            }
-            create("foo") {
-                other = "types:"
-            }
-            create("bar", SpecialNamedThing) {
-                other = "types:"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "types: Named NamedThing"
-        container.getByName("bar").other == "types: Named NamedThing Special SpecialNamedThing"
-    }
-
-    def "can register mutate rule for all items"() {
-        when:
-        mutate {
-            all {
-                assert other == "original"
-                other = "changed"
-            }
-            create("foo") {
-                other = "original"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "changed"
-    }
-
-    def "can register mutate rule for all items with specific type"() {
-        when:
-        mutate {
-            withType(Named) {
-                other += " Named"
-            }
-            withType(String) {
-                other += " String"
-            }
-            withType(Special) {
-                other += " Special"
-            }
-            withType(SpecialNamedThing) {
-                other += " SpecialNamedThing"
-            }
-            create("foo") {
-                other = "foo:"
-            }
-            create("bar", SpecialNamedThing) {
-                other = "bar:"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "foo: Named"
-        container.getByName("bar").other == "bar: Named Special SpecialNamedThing"
-    }
-
-    def "can register defaults rule for all items"() {
-        when:
-        mutate {
-            all {
-                other += " all{}"
-            }
-            create("foo") {
-                other += " create()"
-            }
-            beforeEach {
-                other = "beforeEach{}"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "beforeEach{} create() all{}"
-    }
-
-    def "can register defaults rule for all items with type"() {
-        when:
-        mutate {
-            beforeEach(Named) {
-                other = "Named"
-            }
-            beforeEach(String) {
-                other += " String"
-            }
-            beforeEach(Special) {
-                other += " Special"
-            }
-            beforeEach(SpecialNamedThing) {
-                other += " SpecialNamedThing"
-            }
-            create("foo") {
-                other += " create(foo)"
-            }
-            create("bar", SpecialNamedThing) {
-                other += " create(bar)"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "Named create(foo)"
-        container.getByName("bar").other == "Named Special SpecialNamedThing create(bar)"
-    }
-
-    def "can register finalize rule for all items"() {
-        when:
-        mutate {
-            all {
-                other += " all{}"
-            }
-            afterEach {
-                other += " afterEach{}"
-            }
-            create("foo") {
-                other = "create()"
-            }
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "create() all{} afterEach{}"
-    }
-
-    def "provides groovy DSL"() {
-        when:
-        mutate {
-            foo {
-                assert other == "original"
-                other = "changed"
-            }
-            foo(NamedThing) {
-                other = "original"
-            }
-            bar(SpecialNamedThing)
-        }
-        realize()
-
-        then:
-        container.getByName("foo").other == "changed"
-        container.getByName("bar") instanceof SpecialNamedThing
-    }
-
-    class MutableValue {
-        String value
-    }
-
-    class Bean {
-        String name
-        String value
-    }
-
-    class SpecialBean extends Bean {
-        String other
-    }
-
-    def "sensible error is thrown when trying to apply a class that does not extend RuleSource as a scoped rule"() {
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(MutableValue))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(ModelType.of(MutableValue))
-        def iRef = ModelReference.of("instantiator", iType)
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new MutableValue() }).build())
-                .collection("values", MutableValue, iRef)
-                .mutate {
-            it.descriptor("mutating elements").path "values" type cbType action { c ->
-                c.create("element")
-                c.named("element", Object)
-            }
-        }
-
-        when:
-        registry.realize(ModelPath.path("values"), ModelType.UNTYPED)
-
-        then:
-        ModelRuleExecutionException e = thrown()
-        e.cause.class == InvalidModelRuleDeclarationException
-        e.cause.message == "Type java.lang.Object is not a valid model rule source: rule source classes must directly extend org.gradle.model.RuleSource"
-    }
-
-    static class ElementRules extends RuleSource {
-        @Mutate
-        void connectElementToInput(Bean element, String input) {
-            element.value = input
-        }
-    }
-
-    def "inputs of a rule from an inner source are not realised if the rule is not required"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-        def events = []
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) } as NamedEntityInstantiator).build())
-                .create("input", "input") { events << "input created" }
-                .collection("beans", Bean, iRef)
-                .mutate {
-            it.path "beans" type cbType action { c ->
-                events << "collection mutated"
-                c.create("element") { events << "$it.name created" }
-                c.named("element", ElementRules)
-            }
-
-        }
-
-        when:
-        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
-
-        then:
-        events == ["collection mutated"]
-
-        when:
-        registry.atState(ModelPath.path("beans"), ModelNode.State.GraphClosed)
-
-        then:
-        events == ["collection mutated", "element created", "input created"]
-    }
-
-    def "model rule with by-path dependency on non task related collection element's child that does exist passes validation"() {
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) } as NamedEntityInstantiator).build())
-                .createInstance("foo", new Bean())
-                .mutate {
-            it.path("foo").type(Bean).action("beans.element.mutable", ModelType.of(MutableValue)) { Bean subject, MutableValue input ->
-                subject.value = input.value
-            }
-        }
-        .collection("beans", Bean, iRef)
-                .mutate {
-            it.path "beans" type cbType action { c ->
-                c.create("element")
-            }
-        }
-        .mutate {
-            it.path "beans.element" node {
-                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue(value: "bar")))
-            }
-        }
-
-        when:
-        registry.bindAllReferences()
-
-        then:
-        noExceptionThrown()
-    }
-
-    static class ByTypeSubjectBoundToScopeChildRule extends RuleSource {
-        @Mutate
-        void mutateScopeChild(MutableValue value) {
-            value.value = "foo"
-        }
-    }
-
-    def "model rule with by-type dependency on non task related collection element's child that does exist passes validation"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) } as NamedEntityInstantiator).build())
-                .collection("beans", Bean, iRef)
-                .mutate {
-            it.path "beans" type cbType action { c ->
-                c.create("element")
-                c.named("element", ByTypeSubjectBoundToScopeChildRule)
-            }
-        }
-        .mutate {
-            it.path "beans.element" node {
-                it.addLink(registry.instanceCreator("beans.element.mutable", new MutableValue()))
-            }
-        }
-
-        when:
-        registry.bindAllReferences()
-
-        then:
-        noExceptionThrown()
-    }
-
-    def "adding an unbound scoped rule for an element that is never created results in an error upon validation if the scope parent has been self closed"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) }).build())
-                .collection("beans", Bean, iRef)
-                .mutate {
-            it.path "beans" type cbType action { c ->
-                c.named("element", ElementRules)
-            }
-        }
-
-        when:
-        registry.atState(ModelPath.path("beans"), ModelNode.State.SelfClosed)
-        registry.bindAllReferences()
-
-        then:
-        UnboundModelRulesException e = thrown()
-        normaliseLineSeparators(e.message) == """The following model rules are unbound:
-  $ElementRules.name#connectElementToInput($Bean.name, $String.name)
-    Mutable:
-      - <unspecified> ($Bean.name) parameter 1 in scope of 'beans.element\'
-    Immutable:
-      - <unspecified> ($String.name) parameter 2"""
-    }
-
-    static class SetOther extends RuleSource {
-        @Mutate
-        void set(SpecialBean bean, String other) {
-            bean.other = other
-            bean.value = "changed"
-        }
-    }
-
-    def "can add rule source to all items of type"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-        NamedEntityInstantiator instantiator = { name, type ->
-            (type ?: Bean).newInstance(name: name)
-        }
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, instantiator).build())
-                .collection("beans", Bean, iRef)
-                .createInstance("s", "other")
-                .mutate {
-            it.path("beans").type(cbType).action { c ->
-                c.create("b1", Bean)
-                c.create("b2", Bean)
-                c.create("sb1", SpecialBean)
-                c.create("sb2", SpecialBean)
-                c.withType(SpecialBean, SetOther)
-            }
-        }
-
-        expect:
-        registry.node("s").state == ModelNode.State.Known
-
-        when:
-        registry.atState("beans", ModelNode.State.SelfClosed)
-
-        then:
-        registry.node("s").state == ModelNode.State.Known
-        registry.get("beans.b1", Bean).value != "changed"
-        registry.node("s").state == ModelNode.State.Known
-
-        when:
-        def sb2 = registry.get("beans.sb2", SpecialBean)
-
-        then:
-        sb2.other == "other"
-        registry.node("s").state == ModelNode.State.GraphClosed
-
-        when:
-        def sb1 = registry.get("beans.sb1", SpecialBean)
-
-        then:
-        sb1.other == "other"
-    }
-
-    static class SetProp extends RuleSource {
-        @Mutate
-        void m(@Path("foo") Bean bean) {}
-    }
-
-    def "when targeting by type, paths are interpreted relative to item"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-        NamedEntityInstantiator instantiator = { name, type ->
-            (type ?: Bean).newInstance(name: name)
-        }
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, instantiator).build())
-                .collection("beans", Bean, iRef)
-                .createInstance("s", "other")
-                .mutate {
-            it.path("beans").type(cbType).action { c ->
-                c.create("b1", Bean)
-                c.create("sb1", SpecialBean)
-                c.withType(SpecialBean, SetProp)
-            }
-        }
-
-        when:
-        registry.atState("beans", ModelNode.State.SelfClosed)
-        registry.get("beans.sb1", SpecialBean)
-        registry.bindAllReferences()
-
-        then:
-        UnboundModelRulesException e = thrown()
-        e.rules.size() == 1
-        e.rules.first().mutableInputs.first().path == "beans.sb1.foo"
-    }
-
-    static class SetValue extends RuleSource {
-        @Mutate
-        void set(Bean bean) {
-            bean.value = "changed"
-        }
-    }
-
-    def "when targeting by type, can have rule use more general type than target"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-        NamedEntityInstantiator instantiator = { name, type ->
-            (type ?: Bean).newInstance(name: name)
-        }
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, instantiator).build())
-                .collection("beans", Bean, iRef)
-                .createInstance("s", "other")
-                .mutate {
-            it.path("beans").type(cbType).action { c ->
-                c.create("sb1", SpecialBean)
-                c.withType(SpecialBean, SetValue)
-            }
-        }
-
-        when:
-        registry.atState("beans", ModelNode.State.SelfClosed)
-
-        then:
-        registry.get("beans.sb1", SpecialBean).value == "changed"
-    }
-
-    def "when targeting by type, can have rule use more specific type than target"() {
-        given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
-        NamedEntityInstantiator instantiator = { name, type ->
-            (type ?: Bean).newInstance(name: name)
-        }
-
-        registry
-                .create(ModelCreators.bridgedInstance(iRef, instantiator).build())
-                .collection("beans", Bean, iRef)
-                .createInstance("s", "other")
-                .mutate {
-            it.path("beans").type(cbType).action { c ->
-                c.create("sb1", SpecialBean)
-                c.withType(Bean, SetOther)
-            }
-        }
-
-        when:
-        registry.atState("beans", ModelNode.State.SelfClosed)
-
-        then:
-        registry.get("beans.sb1", SpecialBean).other == "other"
-    }
-
-}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/HasDependencies.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/HasDependencies.groovy
deleted file mode 100644
index 25f05c7..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/HasDependencies.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.collection.internal
-
-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)
- at interface HasDependencies {
-}
\ No newline at end of file
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/Special.java b/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/Special.java
deleted file mode 100644
index 34f23ca..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/collection/internal/Special.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.model.collection.internal;
-
-/**
- * Out on its own due to http://jira.codehaus.org/browse/GROOVY-7010
- */
-interface Special {
-}
\ No newline at end of file
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelMapGroovyDecoratorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelMapGroovyDecoratorTest.groovy
new file mode 100644
index 0000000..af1e370
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelMapGroovyDecoratorTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core
+
+import org.gradle.model.ModelMap
+import spock.lang.Specification
+
+class ModelMapGroovyDecoratorTest extends Specification {
+
+    def target = Mock(ModelMap)
+    def collection = new ModelMapGroovyDecorator<String>(target)
+
+    def "delegates"() {
+        when:
+        collection.create("thing")
+
+        then:
+        target.create("thing")
+    }
+
+    def "wraps derivatives"() {
+        expect:
+        collection.withType(Integer) instanceof ModelMapGroovyDecorator
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelPathTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelPathTest.groovy
index 4530e2b..c54b01a 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelPathTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelPathTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.model.internal.core
 
+import org.gradle.util.Matchers
 import spock.lang.Specification
 
 class ModelPathTest extends Specification {
@@ -27,7 +28,6 @@ class ModelPathTest extends Specification {
         path.components == []
         path.name == ""
         path.depth == 0
-        !path.topLevel
         path.parent == null
         path.rootParent == null
     }
@@ -40,12 +40,37 @@ class ModelPathTest extends Specification {
         path.components == ["p"]
         path.name == "p"
         path.depth == 1
-        path.topLevel
         path.parent == ModelPath.ROOT
         path.rootParent == null
         path == ModelPath.ROOT.child("p")
     }
 
+    def "path with multiple components"() {
+        def path = ModelPath.path(["a", "b", "c"])
+
+        expect:
+        path.toString() == "a.b.c"
+        path.components == ["a", "b", "c"]
+        path.name == "c"
+        path.depth == 3
+        path.parent == ModelPath.path(["a", "b"])
+        path.parent.toString() == "a.b"
+        path.rootParent == ModelPath.path("a")
+        path == ModelPath.ROOT.child("a").child("b").child("c")
+    }
+
+    def "equals and hashcode"() {
+        def p1 = ModelPath.path("a")
+
+        expect:
+        Matchers.strictlyEquals(p1, ModelPath.path("a"))
+        p1 != ModelPath.path(("b"))
+        p1 != ModelPath.path(("abc"))
+        p1 != ModelPath.path(("a.b"))
+        p1 != p1.parent
+        p1 != p1.child("b")
+    }
+
     def "can create path with separator in one component"() {
         def parent = ModelPath.path([name])
         def path = ModelPath.path([name, name])
@@ -55,7 +80,6 @@ class ModelPathTest extends Specification {
         parent.components == [name]
         parent.name == name
         parent.toString() == name
-        parent.topLevel
         parent.depth == 1
         parent.parent == ModelPath.ROOT
         parent.rootParent == null
@@ -98,19 +122,37 @@ class ModelPathTest extends Specification {
         "file.txt" | _
     }
 
-    def "direct child"() {
+    def "is direct child"() {
         expect:
         ModelPath.ROOT.isDirectChild(ModelPath.path("p"))
         !ModelPath.ROOT.isDirectChild(ModelPath.ROOT)
         !ModelPath.ROOT.isDirectChild(ModelPath.path("a.b"))
 
         ModelPath.path("a.b").isDirectChild(ModelPath.path("a.b.c"))
+        !ModelPath.path("a.b").isDirectChild(ModelPath.path("a.b.c.d"))
         !ModelPath.path("a.b").isDirectChild(ModelPath.path("a.a.b"))
         !ModelPath.path("a.b").isDirectChild(ModelPath.path("a.b"))
         !ModelPath.path("a.b").isDirectChild(ModelPath.path("a"))
+        !ModelPath.path("a.b").isDirectChild(ModelPath.path("c.a.b.c"))
         !ModelPath.path("a.b").isDirectChild(null)
     }
 
+    def "is descendant"() {
+        expect:
+        ModelPath.ROOT.isDescendant(ModelPath.path("p"))
+        ModelPath.ROOT.isDescendant(ModelPath.path("a.b"))
+        !ModelPath.ROOT.isDescendant(ModelPath.ROOT)
+
+        ModelPath.path("a.b").isDescendant(ModelPath.path("a.b.c"))
+        ModelPath.path("a.b").isDescendant(ModelPath.path("a.b.c.d.e"))
+        !ModelPath.path("a.b").isDescendant(ModelPath.path("a.a"))
+        !ModelPath.path("a.b").isDescendant(ModelPath.path("a.a.b"))
+        !ModelPath.path("a.b").isDescendant(ModelPath.path("a.b"))
+        !ModelPath.path("a.b").isDescendant(ModelPath.path("a"))
+        !ModelPath.path("a.b").isDescendant(ModelPath.path("c.a.b.c"))
+        !ModelPath.path("a.b").isDescendant(null)
+    }
+
     def "can create paths for indirect descendants"() {
         expect:
         ModelPath.ROOT.descendant(ModelPath.path("c.d")) == ModelPath.path("c.d")
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelReferenceTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelReferenceTest.groovy
new file mode 100644
index 0000000..d2768f7
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelReferenceTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core
+
+import org.gradle.model.internal.type.ModelType
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class ModelReferenceTest extends Specification {
+    def "any"() {
+        expect:
+        def reference = ModelReference.any()
+        reference.scope == null
+        reference.path == null
+        reference.type == ModelType.of(Object)
+        reference.untyped
+        reference.state == ModelNode.State.GraphClosed
+    }
+
+    def "path only"() {
+        expect:
+        def reference = ModelReference.of("some.path")
+        reference.scope == null
+        reference.path == ModelPath.path("some.path")
+        reference.type == ModelType.of(Object)
+        reference.untyped
+        reference.state == ModelNode.State.GraphClosed
+    }
+
+    def "type only"() {
+        expect:
+        def reference = ModelReference.of(String)
+        reference.scope == null
+        reference.path == null
+        reference.type == ModelType.of(String)
+        !reference.untyped
+        reference.state == ModelNode.State.GraphClosed
+    }
+
+    def "can replace state"() {
+        expect:
+        def reference = ModelReference.of("some.path", String).atState(ModelNode.State.Mutated)
+        reference.scope == null
+        reference.path == ModelPath.path("some.path")
+        reference.type == ModelType.of(String)
+        !reference.untyped
+        reference.state == ModelNode.State.Mutated
+
+        def original = ModelReference.of(String)
+        original.atState(original.state).is(original)
+    }
+
+    def "can replace path"() {
+        expect:
+        def reference = ModelReference.of(ModelPath.path("some.path"), ModelType.of(String), ModelNode.State.Mutated).withPath(ModelPath.path("other.path"))
+        reference.scope == null
+        reference.path == ModelPath.path("other.path")
+        reference.type == ModelType.of(String)
+        !reference.untyped
+        reference.state == ModelNode.State.Mutated
+    }
+
+    def "can attach a scope"() {
+        expect:
+        def reference = ModelReference.of(String).inScope(ModelPath.path("some.scope"))
+        reference.scope == ModelPath.path("some.scope")
+        reference.path == null
+        reference.type == ModelType.of(String)
+        !reference.untyped
+        reference.state == ModelNode.State.GraphClosed
+    }
+
+    def "equals"() {
+        expect:
+        def path = ModelReference.of("path")
+        def type = ModelReference.of(String)
+        def pathAndType = ModelReference.of("path", String)
+        def scopeAndType = ModelReference.of(String).inScope(ModelPath.path("scope"))
+
+        Matchers.strictlyEquals(path, ModelReference.of("path"))
+        Matchers.strictlyEquals(type, ModelReference.of(String))
+        Matchers.strictlyEquals(pathAndType, ModelReference.of("path", String))
+        Matchers.strictlyEquals(scopeAndType, type.inScope(ModelPath.path("scope")))
+
+        path != type
+        path != pathAndType
+        path != scopeAndType
+        type != pathAndType
+        type != scopeAndType
+
+        path != ModelReference.of("other")
+        type != ModelReference.of(Long)
+        pathAndType != pathAndType.withPath(ModelPath.path("other"))
+        pathAndType != ModelReference.of("path", Long)
+        scopeAndType != ModelReference.of(Long).inScope(ModelPath.path("scope"))
+        scopeAndType != scopeAndType.inScope(ModelPath.path("other"))
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeJavaTest.java b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeJavaTest.java
index 2cc8255..6f02893 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeJavaTest.java
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeJavaTest.java
@@ -19,6 +19,7 @@ package org.gradle.model.internal.core;
 import org.gradle.model.internal.type.ModelType;
 import org.junit.Test;
 
+import java.util.List;
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
@@ -27,6 +28,24 @@ import static org.junit.Assert.assertEquals;
  * Tests that need full static typing.
  */
 public class ModelTypeJavaTest {
+    class Nested<T> {
+        class Child<S extends Number & Runnable> { }
+    }
+
+    @Test
+    public void testNestedParameterizedType() {
+// Suppress - checkstyle gets confused with type params on the outer type
+//CHECKSTYLE:OFF
+        ModelType<?> type = new ModelType<Nested<? super Long>.Child<? extends Runnable>>() {};
+        assertEquals(type.getSimpleName(), "ModelTypeJavaTest.Nested<? super Long>.Child<? extends Runnable>");
+        assertEquals(type.toString(), "org.gradle.model.internal.core.ModelTypeJavaTest.Nested<? super java.lang.Long>.Child<? extends java.lang.Runnable>");
+
+        ModelType<?> listType = new ModelType<List<? extends Nested<Number>.Child<? extends Runnable>>>() {};
+
+        assertEquals(listType.getSimpleName(), "List<? extends ModelTypeJavaTest.Nested<Number>.Child<? extends Runnable>>");
+        assertEquals(listType.toString(), "java.util.List<? extends org.gradle.model.internal.core.ModelTypeJavaTest.Nested<java.lang.Number>.Child<? extends java.lang.Runnable>>");
+//CHECKSTYLE:ON
+    }
 
     @Test
     public void testBuildType() throws Exception {
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeTest.groovy
index 7cbe397..630fd1b 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/ModelTypeTest.groovy
@@ -20,6 +20,18 @@ import org.gradle.model.internal.type.ModelType
 import spock.lang.Specification
 
 class ModelTypeTest extends Specification {
+    class Nested {}
+
+    def "represents classes"() {
+        expect:
+        def type = ModelType.of(String)
+        type.toString() == String.name
+        type.simpleName == String.simpleName
+
+        def nested = ModelType.of(Nested)
+        nested.toString() == Nested.name
+        nested.simpleName == "ModelTypeTest.Nested"
+    }
 
     def "represents type variables"() {
         when:
@@ -30,6 +42,10 @@ class ModelTypeTest extends Specification {
         type.typeVariables[1] == new ModelType<Map<Integer, Float>>() {}
         type.typeVariables[1].typeVariables[0] == ModelType.of(Integer)
         type.typeVariables[1].typeVariables[1] == ModelType.of(Float)
+
+        and:
+        type.toString() == "java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.lang.Float>>"
+        type.simpleName == "Map<String, Map<Integer, Float>>"
     }
 
     def "generic type compatibility"() {
@@ -66,14 +82,18 @@ class ModelTypeTest extends Specification {
 
     def m3(List<?> anything) {}
 
+    def m4(List<? extends Object> objects) {}
+
     def "wildcards"() {
         def extendsString = ModelType.paramType(getClass().getDeclaredMethod("m1", List.class), 0).typeVariables[0]
         def superString = ModelType.paramType(getClass().getDeclaredMethod("m2", List.class), 0).typeVariables[0]
         def anything = ModelType.paramType(getClass().getDeclaredMethod("m3", List.class), 0).typeVariables[0]
+        def objects = ModelType.paramType(getClass().getDeclaredMethod("m4", List.class), 0).typeVariables[0]
 
         expect:
         extendsString.wildcard
         superString.wildcard
+        objects.wildcard
         anything.wildcard
 
         extendsString.upperBound == ModelType.of(String)
@@ -82,8 +102,21 @@ class ModelTypeTest extends Specification {
         superString.upperBound == null
         superString.lowerBound == ModelType.of(String)
 
+        objects.upperBound == null
+        objects.lowerBound == null
+
         anything.upperBound == null
         anything.lowerBound == null
+
+        extendsString.toString() == "? extends java.lang.String"
+        superString.toString() == "? super java.lang.String"
+        objects.toString() == "?"
+        anything.toString() == "?"
+
+        extendsString.simpleName == "? extends String"
+        superString.simpleName == "? super String"
+        objects.simpleName == "?"
+        anything.simpleName == "?"
     }
 
     def "isSubclass"() {
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/NamedEntityInstantiatorsTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/NamedEntityInstantiatorsTest.groovy
new file mode 100644
index 0000000..f70c573
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/NamedEntityInstantiatorsTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core
+
+import spock.lang.Specification
+
+class NamedEntityInstantiatorsTest extends Specification {
+
+    static class Base {}
+    static class NonSubtype {}
+    static class NonSubtypeChild extends NonSubtype {}
+
+
+    def "non subtype instantiator always throws"() {
+        given:
+        def instantiator = NamedEntityInstantiators.nonSubtype(NonSubtype, Base)
+
+        when:
+        instantiator.create("foo", NonSubtypeChild)
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Cannot create an item of type ${NonSubtypeChild.name} as this is not a subtype of ${Base.name}."
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactoryTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactoryTest.groovy
new file mode 100644
index 0000000..bde35c9
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/core/rule/describe/StandardDescriptorFactoryTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core.rule.describe
+
+import spock.lang.Specification
+
+class StandardDescriptorFactoryTest extends Specification {
+
+    def "generates a new descriptor using string as a base"() {
+        when:
+        def factory = new StandardDescriptorFactory("foo")
+
+        then:
+        factory.transform("bar") == "foo.bar()"
+    }
+
+    def "generates a new descriptor using another descriptor as a base"() {
+        when:
+        def factory = new StandardDescriptorFactory(new SimpleModelRuleDescriptor("foo"))
+
+        then:
+        factory.transform("bar") == "foo.bar()"
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/inspect/ModelRuleExtractorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/inspect/ModelRuleExtractorTest.groovy
index 7659c4c..fb805be 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/inspect/ModelRuleExtractorTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/inspect/ModelRuleExtractorTest.groovy
@@ -18,7 +18,6 @@ package org.gradle.model.internal.inspect
 
 import org.codehaus.groovy.reflection.ClassInfo
 import org.gradle.model.*
-import org.gradle.model.collection.CollectionBuilder
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelCreators
 import org.gradle.model.internal.core.ModelPath
@@ -462,20 +461,20 @@ ${ManagedWithNonManageableParents.name}
         invalidTypeName = "$ParametrizedManaged.name<$String.name>"
     }
 
-    static class HasRuleWithUncheckedCollectionBuilder extends RuleSource {
+    static class HasRuleWithUncheckedModelMap extends RuleSource {
         @Model
-        static ModelThing modelPath(CollectionBuilder foo) {
+        static ModelThing modelPath(ModelMap foo) {
             new ModelThing("foo")
         }
     }
 
-    def "error when trying to use collection builder without specifying type param"() {
+    def "error when trying to use model map without specifying type param"() {
         when:
-        registerRules(HasRuleWithUncheckedCollectionBuilder)
+        registerRules(HasRuleWithUncheckedModelMap)
 
         then:
         InvalidModelRuleDeclarationException e = thrown()
-        e.message == "$HasRuleWithUncheckedCollectionBuilder.name#modelPath(org.gradle.model.collection.CollectionBuilder) is not a valid model rule method: raw type org.gradle.model.collection.CollectionBuilder used for parameter 1 (all type parameters must be specified of parameterized type)"
+        e.message == "$HasRuleWithUncheckedModelMap.name#modelPath(org.gradle.model.ModelMap) is not a valid model rule method: raw type org.gradle.model.ModelMap used for parameter 1 (all type parameters must be specified of parameterized type)"
     }
 
     def "extracted rules are cached"() {
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ManagedSetModelProjectionTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ManagedSetModelProjectionTest.groovy
deleted file mode 100644
index 49df419..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ManagedSetModelProjectionTest.groovy
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.model.internal.manage.projection
-
-import org.gradle.api.internal.ClosureBackedAction
-import org.gradle.internal.BiAction
-import org.gradle.model.Managed
-import org.gradle.model.ModelViewClosedException
-import org.gradle.model.collection.ManagedSet
-import org.gradle.model.internal.core.ModelPath
-import org.gradle.model.internal.core.ModelReference
-import org.gradle.model.internal.core.ModelRuleExecutionException
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
-import org.gradle.model.internal.fixture.ModelRegistryHelper
-import org.gradle.model.internal.inspect.DefaultModelCreatorFactory
-import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
-import org.gradle.model.internal.type.ModelType
-import spock.lang.Specification
-import spock.lang.Unroll
-
-class ManagedSetModelProjectionTest extends Specification {
-    @Managed
-    interface NamedThing {
-        String getName()
-
-        void setName(String name);
-
-        String getValue()
-
-        void setValue(String value)
-    }
-
-    def collectionPath = ModelPath.path("collection")
-    def collectionType = new ModelType<ManagedSet<NamedThing>>() {}
-    def schemaStore = DefaultModelSchemaStore.instance
-    def factory = new DefaultModelCreatorFactory(schemaStore)
-    def registry = new ModelRegistryHelper()
-    private ModelReference<ManagedSet<NamedThing>> reference = ModelReference.of(collectionPath, new ModelType<ManagedSet<NamedThing>>() {})
-
-    def setup() {
-        registry.create(
-                factory.creator(
-                        new SimpleModelRuleDescriptor("define collection"),
-                        collectionPath,
-                        schemaStore.getSchema(collectionType),
-                        [],
-                        { value, inputs -> } as BiAction)
-        )
-    }
-
-    void mutate(@DelegatesTo(ManagedSet) Closure<?> action) {
-        registry.mutate(reference, new ClosureBackedAction<>(action))
-        registry.realizeNode(collectionPath)
-    }
-
-    def "can define and query elements"() {
-        when:
-        mutate {
-            create { name = '1' }
-            create { name = '2' }
-        }
-
-        then:
-        def set = registry.realize(collectionPath, collectionType)
-        set*.name == ['1', '2']
-        set.toArray().collect { it.name } == ['1', '2']
-        set.toArray(new NamedThing[2]).collect { it.name } == ['1', '2']
-    }
-
-    def "reuses element views"() {
-        when:
-        mutate {
-            create { name = '1' }
-            create { name = '2' }
-        }
-
-        then:
-        def set = registry.realize(collectionPath, collectionType)
-        def e1 = set.find { it.name == '1' }
-        def e2 = set.find { it.name == '1' }
-        e1.is(e2)
-    }
-
-    def "can query set size"() {
-        when:
-        mutate {
-            create { name = '1' }
-            create { name = '2' }
-        }
-
-        then:
-        !registry.realize(collectionPath, collectionType).isEmpty()
-        registry.realize(collectionPath, collectionType).size() == 2
-    }
-
-    def "can query set membership"() {
-        when:
-        mutate {
-            create { name = '1' }
-            create { name = '2' }
-        }
-
-        then:
-        def set = registry.realize(collectionPath, collectionType)
-        set.contains(set.find { it.name == '1' })
-        !set.contains("green")
-        !set.contains({} as NamedThing)
-
-        set.containsAll(set)
-        set.containsAll(set as List)
-        set.containsAll(set.findAll { it.name == '1' })
-        !set.containsAll(["green"])
-        !set.containsAll([{} as NamedThing])
-    }
-
-    def "can configure children"() {
-        when:
-        mutate {
-            afterEach {
-                value += " after"
-            }
-            create { name = '1' }
-            beforeEach {
-                value = "before"
-            }
-            create { name = '2' }
-        }
-
-        then:
-        def set = registry.realize(collectionPath, collectionType).toList()
-        set[0].value == "before after"
-        set[1].value == "before after"
-    }
-
-    @Unroll
-    def "cannot configure children when used as an input - #method"() {
-        when:
-        registry.createInstance("things", []).mutate {
-            it.path("things").action(reference.path, reference.type, { things, set ->
-                set."$method" {
-
-                }
-            })
-        }
-
-        registry.get("things")
-
-        then:
-        def e = thrown ModelRuleExecutionException
-        e.cause instanceof ModelViewClosedException
-
-        where:
-        method << ["afterEach", "beforeEach"]
-    }
-
-}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ModelSetModelProjectionTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ModelSetModelProjectionTest.groovy
new file mode 100644
index 0000000..7f045fb
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/projection/ModelSetModelProjectionTest.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.model.internal.manage.projection
+
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.internal.BiAction
+import org.gradle.model.Managed
+import org.gradle.model.ModelViewClosedException
+import org.gradle.model.ModelSet
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.core.ModelRuleExecutionException
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.model.internal.inspect.DefaultModelCreatorFactory
+import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
+import org.gradle.model.internal.type.ModelType
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ModelSetModelProjectionTest extends Specification {
+    @Managed
+    interface NamedThing {
+        String getName()
+
+        void setName(String name);
+
+        String getValue()
+
+        void setValue(String value)
+    }
+
+    def collectionPath = ModelPath.path("collection")
+    def collectionType = new ModelType<ModelSet<NamedThing>>() {}
+    def schemaStore = DefaultModelSchemaStore.instance
+    def factory = new DefaultModelCreatorFactory(schemaStore)
+    def registry = new ModelRegistryHelper()
+    private ModelReference<ModelSet<NamedThing>> reference = ModelReference.of(collectionPath, new ModelType<ModelSet<NamedThing>>() {})
+
+    def setup() {
+        registry.create(
+                factory.creator(
+                        new SimpleModelRuleDescriptor("define collection"),
+                        collectionPath,
+                        schemaStore.getSchema(collectionType),
+                        [],
+                        { value, inputs -> } as BiAction)
+        )
+    }
+
+    void mutate(@DelegatesTo(ModelSet) Closure<?> action) {
+        registry.mutate(reference, new ClosureBackedAction<>(action))
+        registry.realizeNode(collectionPath)
+    }
+
+    def "can define and query elements"() {
+        when:
+        mutate {
+            create { name = '1' }
+            create { name = '2' }
+        }
+
+        then:
+        def set = registry.realize(collectionPath, collectionType)
+        set*.name == ['1', '2']
+        set.toArray().collect { it.name } == ['1', '2']
+        set.toArray(new NamedThing[2]).collect { it.name } == ['1', '2']
+    }
+
+    def "reuses element views"() {
+        when:
+        mutate {
+            create { name = '1' }
+            create { name = '2' }
+        }
+
+        then:
+        def set = registry.realize(collectionPath, collectionType)
+        def e1 = set.find { it.name == '1' }
+        def e2 = set.find { it.name == '1' }
+        e1.is(e2)
+    }
+
+    def "can query set size"() {
+        when:
+        mutate {
+            create { name = '1' }
+            create { name = '2' }
+        }
+
+        then:
+        !registry.realize(collectionPath, collectionType).isEmpty()
+        registry.realize(collectionPath, collectionType).size() == 2
+    }
+
+    def "can query set membership"() {
+        when:
+        mutate {
+            create { name = '1' }
+            create { name = '2' }
+        }
+
+        then:
+        def set = registry.realize(collectionPath, collectionType)
+        set.contains(set.find { it.name == '1' })
+        !set.contains("green")
+        !set.contains({} as NamedThing)
+
+        set.containsAll(set)
+        set.containsAll(set as List)
+        set.containsAll(set.findAll { it.name == '1' })
+        !set.containsAll(["green"])
+        !set.containsAll([{} as NamedThing])
+    }
+
+    def "can configure children"() {
+        when:
+        mutate {
+            afterEach {
+                value += " after"
+            }
+            create { name = '1' }
+            beforeEach {
+                value = "before"
+            }
+            create { name = '2' }
+        }
+
+        then:
+        def set = registry.realize(collectionPath, collectionType).toList()
+        set[0].value == "before after"
+        set[1].value == "before after"
+    }
+
+    @Unroll
+    def "cannot configure children when used as an input - #method"() {
+        when:
+        registry.createInstance("things", []).mutate {
+            it.path("things").action(reference.path, reference.type, { things, set ->
+                set."$method" {
+
+                }
+            })
+        }
+
+        registry.get("things")
+
+        then:
+        def e = thrown ModelRuleExecutionException
+        e.cause instanceof ModelViewClosedException
+
+        where:
+        method << ["afterEach", "beforeEach"]
+    }
+
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStoreTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStoreTest.groovy
index 3c99f11..d7d7930 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStoreTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStoreTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.model.internal.manage.schema.extract
 import groovy.transform.CompileStatic
 import org.codehaus.groovy.reflection.ClassInfo
 import org.gradle.model.Managed
-import org.gradle.model.collection.ManagedSet
+import org.gradle.model.ModelSet
 import org.gradle.model.internal.manage.schema.ModelStructSchema
 import org.gradle.model.internal.type.ModelType
 import spock.lang.Specification
@@ -64,8 +64,8 @@ class DefaultModelSchemaStoreTest extends Specification {
         impl << [
                 "class SomeThing {}",
                 "@${Managed.name} interface SomeThing { SomeThing getThing() }",
-                "@${Managed.name} interface SomeThing { ${ManagedSet.name}<SomeThing> getThings() }",
-                "@${Managed.name} interface SomeThing { @${Managed.name} static interface Child {}; ${ManagedSet.name}<Child> getThings() }",
+                "@${Managed.name} interface SomeThing { ${ModelSet.name}<SomeThing> getThings() }",
+                "@${Managed.name} interface SomeThing { @${Managed.name} static interface Child {}; ${ModelSet.name}<Child> getThings() }",
         ]
     }
 
@@ -117,8 +117,8 @@ class DefaultModelSchemaStoreTest extends Specification {
         when:
         def cl = new GroovyClassLoader()
         addClass(cl, "@${Managed.name} interface Thing {}")
-        addClass(cl, "@${Managed.name} interface Container1 { ${ManagedSet.name}<Thing> getThings() }")
-        addClass(cl, "@${Managed.name} interface Container2 { ${ManagedSet.name}<Thing> getThings() }")
+        addClass(cl, "@${Managed.name} interface Container1 { ${ModelSet.name}<Thing> getThings() }")
+        addClass(cl, "@${Managed.name} interface Container2 { ${ModelSet.name}<Thing> getThings() }")
 
         then:
         store.cache.size() == 4
@@ -129,8 +129,8 @@ class DefaultModelSchemaStoreTest extends Specification {
         def cl = new GroovyClassLoader()
         addClass(cl, "@${Managed.name} interface Thing1 {}")
         addClass(cl, "@${Managed.name} interface Thing2 {}")
-        addClass(cl, "@${Managed.name} interface Container1 { ${ManagedSet.name}<Thing1> getThings() }")
-        addClass(cl, "@${Managed.name} interface Container2 { ${ManagedSet.name}<Thing2> getThings() }")
+        addClass(cl, "@${Managed.name} interface Container1 { ${ModelSet.name}<Thing1> getThings() }")
+        addClass(cl, "@${Managed.name} interface Container2 { ${ModelSet.name}<Thing2> getThings() }")
 
         then:
         store.cache.size() == 6
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGeneratorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGeneratorTest.groovy
new file mode 100644
index 0000000..c885c0f
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ManagedCollectionProxyClassGeneratorTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.manage.schema.extract
+
+import spock.lang.Specification
+
+class ManagedCollectionProxyClassGeneratorTest extends Specification {
+    static def generator = new ManagedCollectionProxyClassGenerator()
+    static classes = [:]
+
+    def "generates a proxy class for an interface"() {
+        def target = Stub(SomeType)
+        target.value >> 12
+
+        expect:
+        def impl = newInstance(SomeTypeImpl, SpecializedType, target)
+        impl instanceof SomeTypeImpl
+        impl instanceof SpecializedType
+        impl.value == 12
+    }
+
+    def "generates a proxy classes for multiple different contract types"() {
+        def target = Stub(SomeType)
+        target.value >> 12
+
+        expect:
+        def impl1 = generate(SomeTypeImpl, SpecializedType)
+        def impl2 = generate(SomeTypeImpl, SpecializedType2)
+        impl1 != impl2
+        SpecializedType.isAssignableFrom(impl1)
+        SpecializedType2.isAssignableFrom(impl2)
+    }
+
+    SomeType newInstance(Class<? extends SomeType> implType, Class<? extends SomeType> publicType, SomeType target) {
+        def generated = generate(implType, publicType)
+        return generated.newInstance(target)
+    }
+
+    Class<? extends SomeType> generate(Class<? extends SomeType> implType, Class<? extends SomeType> publicType) {
+        def generated = classes[publicType]
+        if (generated == null) {
+            generated = generator.generate(implType, publicType)
+            classes[publicType] = generated
+        }
+        return generated
+    }
+
+    interface SomeType {
+        Integer getValue()
+
+        void setValue(Integer value)
+    }
+
+    interface SpecializedType extends SomeType {}
+
+    interface SpecializedType2 extends SomeType {}
+
+    static class SomeTypeImpl implements SomeType {
+        SomeType target
+
+        SomeTypeImpl(SomeType target) {
+            this.target = target
+        }
+
+        @Override
+        Integer getValue() {
+            return target.value
+        }
+
+        @Override
+        void setValue(Integer value) {
+            target.value = value
+        }
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractorTest.groovy
index 7a05f56..afee47c 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractorTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/ModelSchemaExtractorTest.groovy
@@ -18,8 +18,10 @@ package org.gradle.model.internal.manage.schema.extract
 
 import org.gradle.internal.reflect.MethodDescription
 import org.gradle.model.Managed
+import org.gradle.model.ModelMap
+import org.gradle.model.ModelSet
 import org.gradle.model.Unmanaged
-import org.gradle.model.collection.ManagedSet
+import org.gradle.model.internal.manage.schema.ModelMapSchema
 import org.gradle.model.internal.manage.schema.ModelSchema
 import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache
 import org.gradle.model.internal.type.ModelType
@@ -32,8 +34,10 @@ import java.util.regex.Pattern
 
 class ModelSchemaExtractorTest extends Specification {
 
+    def classLoader = new GroovyClassLoader(getClass().classLoader)
     def store = new ModelSchemaExtractor()
-    @Shared def cache = new ModelSchemaCache()
+    @Shared
+    def cache = new ModelSchemaCache()
 
     static interface NotAnnotatedInterface {}
 
@@ -172,7 +176,10 @@ class ModelSchemaExtractorTest extends Specification {
 
     def "only selected unmanaged property types are allowed"() {
         expect:
-        fail NonStringProperty, Pattern.quote("an unmanaged type")
+        fail type, Pattern.quote("an unmanaged type")
+
+        where:
+        type << [NonStringProperty, ClassWithExtendedFileType]
     }
 
     @Managed
@@ -393,9 +400,9 @@ class ModelSchemaExtractorTest extends Specification {
     def "conflicting properties of super types are detected"() {
         given:
         def invalidMethods = [
-                MethodDescription.name("getValue").owner(SingleFloatValueProperty).returns(Float).takes(),
-                MethodDescription.name("getValue").owner(SingleIntegerValueProperty).returns(Integer).takes(),
-                MethodDescription.name("getValue").owner(SingleStringValueProperty).returns(String).takes(),
+            MethodDescription.name("getValue").owner(SingleFloatValueProperty).returns(Float).takes(),
+            MethodDescription.name("getValue").owner(SingleIntegerValueProperty).returns(Integer).takes(),
+            MethodDescription.name("getValue").owner(SingleStringValueProperty).returns(String).takes(),
         ]
         def message = Pattern.quote("overloaded methods are not supported (invalid methods: ${invalidMethods.join(", ")})")
 
@@ -452,11 +459,11 @@ class ModelSchemaExtractorTest extends Specification {
         def type = ModelType.returnType(TypeHolder.getDeclaredMethod("noParam"))
 
         expect:
-        fail type, "type parameter of $ManagedSet.name has to be specified"
+        fail type, "type parameter of $ModelSet.name has to be specified"
     }
 
     static interface TypeHolder {
-        ManagedSet noParam();
+        ModelSet noParam();
     }
 
     @Managed
@@ -487,36 +494,37 @@ class ModelSchemaExtractorTest extends Specification {
     @Unroll
     def "type argument of a managed set cannot be a wildcard - #type"() {
         expect:
-        fail type, "type parameter of $ManagedSet.name cannot be a wildcard"
+        fail type, "type parameter of $ModelSet.name cannot be a wildcard"
 
         where:
         type << [
-                new ModelType<ManagedSet<?>>() {},
-                new ModelType<ManagedSet<? extends A1>>() {},
-                new ModelType<ManagedSet<? super A1>>() {}
+            new ModelType<ModelSet<?>>() {},
+            new ModelType<ModelSet<? extends A1>>() {},
+            new ModelType<ModelSet<? super A1>>() {}
         ]
     }
 
     def "type argument of a managed set has to be managed"() {
         given:
-        def type = new ModelType<ManagedSet<Object>>() {}
+        def type = new ModelType<ModelSet<Object>>() {}
 
         when:
         extract(type)
 
         then:
         InvalidManagedModelElementTypeException e = thrown()
-        e.message == TextUtil.toPlatformLineSeparators("""Invalid managed model type ${new ModelType<ManagedSet<Object>>() {}}: cannot create a managed set of type $Object.name as it is an unmanaged type.
+        e.message == TextUtil.toPlatformLineSeparators("""Invalid managed model type ${new ModelType<ModelSet<Object>>() {}}: cannot create a managed set of type $Object.name as it is an unmanaged type.
 Supported types:
  - enum types
- - JDK value types: String, Boolean, Character, Integer, Long, Double, BigInteger, BigDecimal
- - org.gradle.model.collection.ManagedSet<?> of a managed type
- - interfaces and abstract classes annotated with org.gradle.model.Managed""")
+ - JDK value types: String, Boolean, Character, Integer, Long, Double, BigInteger, BigDecimal, File
+ - org.gradle.model.ModelSet<?> of a managed type
+ - interfaces and abstract classes annotated with org.gradle.model.Managed
+ - org.gradle.model.ModelMap<?> of a managed type""")
     }
 
     def "type argument of a managed set has to be a valid managed type"() {
         given:
-        def type = new ModelType<ManagedSet<SetterOnly>>() {}
+        def type = new ModelType<ModelSet<SetterOnly>>() {}
 
         when:
         extract(type)
@@ -532,18 +540,18 @@ $type
 
     def "specializations of managed set are not supported"() {
         given:
-        def type = new ModelType<SpecialManagedSet<A1>>() {}
+        def type = new ModelType<SpecialModelSet<A1>>() {}
 
         expect:
-        fail type, "subtyping $ManagedSet.name is not supported"
+        fail type, "subtyping $ModelSet.name is not supported"
     }
 
     def "managed sets of managed set are not supported"() {
         given:
-        def type = new ModelType<ManagedSet<ManagedSet<A1>>>() {}
+        def type = new ModelType<ModelSet<ModelSet<A1>>>() {}
 
         expect:
-        fail type, "$ManagedSet.name cannot be used as type parameter of $ManagedSet.name"
+        fail type, "$ModelSet.name cannot be used as type parameter of $ModelSet.name"
     }
 
     static class MyBigInteger extends BigInteger {
@@ -639,6 +647,7 @@ $type
     @Managed
     static abstract class NonAbstractGetterWithSetter {
         String getName() {}
+
         abstract void setName(String name)
     }
 
@@ -747,6 +756,7 @@ $type
     @Managed
     static abstract class ProtectedAbstractMethods {
         protected abstract String getName()
+
         protected abstract void setName(String name)
     }
 
@@ -769,6 +779,7 @@ $type
         protected String getName() {
             return null;
         }
+
         private void setName(String name) {}
     }
 
@@ -786,6 +797,17 @@ $type
         fail ProtectedAndPrivateNonAbstractMethodsInSuper, Pattern.quote("protected and private methods are not allowed (invalid methods: $getterDescription, $setterDescription)")
     }
 
+    interface SomeMap extends ModelMap<List<String>> {
+    }
+
+    def "specialized map"() {
+        expect:
+        def schema = extract(SomeMap)
+        schema instanceof ModelMapSchema
+        schema.elementType == new ModelType<List<String>>() {}
+        schema.managedImpl
+    }
+
     private void fail(extractType, errorType, String msgPattern) {
         try {
             extract(extractType)
@@ -811,4 +833,66 @@ $type
     private String getName(Class<?> clazz) {
         clazz.name
     }
-}
\ No newline at end of file
+
+    @Unroll
+    def "can extract a simple managed type with a property of #type"() {
+        when:
+        Class<?> generatedClass = managedClass(type)
+
+        then:
+        extract(generatedClass)
+
+        where:
+        type << [String, Boolean, Character, Integer, Long, Double, BigInteger, BigDecimal, File]
+    }
+
+    @Unroll
+    def "should enforce properties of #type are managed"() {
+        when:
+        Class<?> generatedClass = managedClassWithoutSetter(type)
+
+        then:
+        fail generatedClass, "has non managed type ${type.name}, only managed types can be used"
+
+        where:
+        type << [String, Boolean, Character, Integer, Long, Double, BigInteger, BigDecimal, File]
+    }
+
+    @Managed
+    static interface ClassWithExtendedFileType {
+        ExtendedFile getExtendedFile()
+
+        void setExtendedFile(ExtendedFile extendedFile)
+    }
+
+    static class ExtendedFile extends File {
+        ExtendedFile(String pathname) {
+            super(pathname)
+        }
+    }
+
+    private Class<?> managedClass(Class<?> type) {
+        String typeName = type.getSimpleName()
+        return classLoader.parseClass("""
+import org.gradle.model.Managed
+
+ at Managed
+interface Managed${typeName} {
+    ${typeName} get${typeName}()
+    void set${typeName}(${typeName} a${typeName})
+}
+""")
+    }
+
+    private Class<?> managedClassWithoutSetter(Class<?> type) {
+        String typeName = type.getSimpleName()
+        return classLoader.parseClass("""
+import org.gradle.model.Managed
+
+ at Managed
+interface Managed${typeName} {
+    ${typeName} get${typeName}()
+}
+""")
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialManagedSet.java b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialManagedSet.java
deleted file mode 100644
index e65e2cf..0000000
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialManagedSet.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.model.internal.manage.schema.extract;
-
-import org.gradle.model.collection.ManagedSet;
-
-interface SpecialManagedSet<T> extends ManagedSet<T> {}
\ No newline at end of file
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialModelSet.java b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialModelSet.java
new file mode 100644
index 0000000..7b5c402
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/manage/schema/extract/SpecialModelSet.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.model.internal.manage.schema.extract;
+
+import org.gradle.model.ModelSet;
+
+interface SpecialModelSet<T> extends ModelSet<T> {}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/DefaultModelRegistryTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/DefaultModelRegistryTest.groovy
index dcb7c78..b8c0074 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/DefaultModelRegistryTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/DefaultModelRegistryTest.groovy
@@ -20,9 +20,12 @@ import org.gradle.api.Action
 import org.gradle.api.Transformer
 import org.gradle.internal.BiAction
 import org.gradle.model.ConfigurationCycleException
+import org.gradle.model.InvalidModelRuleException
+import org.gradle.model.ModelRuleBindingException
 import org.gradle.model.internal.core.*
 import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.util.TextUtil
 import spock.lang.Specification
 import spock.lang.Unroll
@@ -63,7 +66,7 @@ class DefaultModelRegistryTest extends Specification {
         registry.realizeNode(ModelPath.ROOT) != null
     }
 
-    def "cannot get element for which creator inputs are not bound"() {
+    def "cannot get element for which creator by-path input does not exist"() {
         given:
         registry.create("foo") { it.descriptor("foo creator").unmanaged(String, "other", null, Stub(Transformer)) }
 
@@ -78,6 +81,37 @@ class DefaultModelRegistryTest extends Specification {
       - other (java.lang.Object)"""
     }
 
+    def "cannot get element for which creator by-type input does not exist"() {
+        given:
+        registry.create("foo") { it.descriptor("foo creator").unmanaged(String, Long, Stub(Transformer)) }
+
+        when:
+        registry.realize(ModelPath.path("foo"), ModelType.untyped())
+
+        then:
+        UnboundModelRulesException e = thrown()
+        normaliseLineSeparators(e.message) == """The following model rules are unbound:
+  foo creator
+    Immutable:
+      - <unspecified> (java.lang.Long)"""
+    }
+
+    def "cannot register creator when by-type input is ambiguous"() {
+        given:
+        registry.createInstance("other-1", 11)
+        registry.createInstance("other-2", 12)
+
+        when:
+        registry.create("foo") { it.descriptor("foo creator").unmanaged(String, Number, Stub(Transformer)) }
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        normaliseLineSeparators(e.cause.message) == """Type-only model reference of type java.lang.Number is ambiguous as multiple model elements are available for this type:
+  - other-1 (created by: other-1 creator)
+  - other-2 (created by: other-2 creator)"""
+    }
+
     def "cannot register creator when element already known"() {
         given:
         registry.create("foo") { it.descriptor("create foo as String").unmanaged("value") }
@@ -104,6 +138,23 @@ class DefaultModelRegistryTest extends Specification {
         e.message == /Cannot create 'foo' using creation rule 'create foo as Integer' as the rule 'create foo as String' has already been used to create this model element./
     }
 
+    def "cannot register creator when sibling with same type used as by-type input"() {
+        given:
+        registry.createInstance("other-1", 12)
+        registry.create("foo") { it.descriptor("foo creator").unmanaged(String, Number, Stub(Transformer)) }
+        registry.realize(ModelPath.path("foo"), ModelType.untyped())
+
+        when:
+        registry.createInstance("other-2", 11)
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        normaliseLineSeparators(e.cause.message) == """Type-only model reference of type java.lang.Number is ambiguous as multiple model elements are available for this type:
+  - other-1 (created by: other-1 creator)
+  - other-2 (created by: other-2 creator)"""
+    }
+
     def "rule cannot add link when element already known"() {
         def mutatorAction = Mock(Action)
 
@@ -162,7 +213,7 @@ class DefaultModelRegistryTest extends Specification {
         registry.realize(ModelPath.path("bar"), ModelType.untyped()) == "[12]"
     }
 
-    def "inputs for creator are bound when inputs later defined by some rule"() {
+    def "parent of input is implicitly closed when input is not known"() {
         def creatorAction = Mock(Transformer)
         def mutatorAction = Mock(Action)
 
@@ -174,7 +225,6 @@ class DefaultModelRegistryTest extends Specification {
         creatorAction.transform(12) >> "[12]"
 
         expect:
-        registry.realize(ModelPath.path("foo"), ModelType.untyped()) // TODO - should not need this - the input can be inferred from the input path
         registry.realize(ModelPath.path("bar"), ModelType.untyped()) == "[12]"
     }
 
@@ -282,6 +332,50 @@ class DefaultModelRegistryTest extends Specification {
         registry.realize(ModelPath.path("bar"), ModelType.of(Bean)).value == "[12]"
     }
 
+    def "transitions elements that depend on a particular state of an element when the target element leaves target state"() {
+        given:
+        registry.createInstance("a", new Bean())
+        registry.createInstance("b", new Bean())
+        registry.configure(ModelActionRole.Finalize) { it.path("b").action(ModelReference.of(ModelPath.path("a"), ModelType.of(Bean), ModelNode.State.DefaultsApplied)) { Bean b, Bean a ->
+            b.value = "$b.value $a.value"
+        }}
+        registry.configure(ModelActionRole.Mutate) { it.path("b").action { Bean b ->
+            b.value = "b-mutate"
+        }}
+        registry.configure(ModelActionRole.Mutate) { it.path("a").action { Bean a ->
+            a.value = "a-mutate"
+        }}
+        registry.configure(ModelActionRole.Defaults) { it.path("a").action { Bean a ->
+            a.value = "a-defaults"
+        }}
+
+        expect:
+        registry.realize(ModelPath.path("a"), ModelType.of(Bean)).value == "a-mutate"
+        registry.realize(ModelPath.path("b"), ModelType.of(Bean)).value == "b-mutate a-defaults"
+    }
+
+    def "transitions input elements to target state"() {
+        given:
+        registry.createInstance("a", new Bean())
+        registry.createInstance("b", new Bean())
+        registry.configure(ModelActionRole.Finalize) { it.path("b").action(ModelReference.of(ModelPath.path("a"), ModelType.of(Bean), ModelNode.State.DefaultsApplied)) { Bean b, Bean a ->
+            b.value = "$b.value $a.value"
+        }}
+        registry.configure(ModelActionRole.Mutate) { it.path("b").action { Bean b ->
+            b.value = "b-mutate"
+        }}
+        registry.configure(ModelActionRole.Mutate) { it.path("a").action { Bean a ->
+            a.value = "a-mutate"
+        }}
+        registry.configure(ModelActionRole.Defaults) { it.path("a").action { Bean a ->
+            a.value = "a-defaults"
+        }}
+
+        expect:
+        registry.realize(ModelPath.path("b"), ModelType.of(Bean)).value == "b-mutate a-defaults"
+        registry.realize(ModelPath.path("a"), ModelType.of(Bean)).value == "a-mutate"
+    }
+
     def "can attach a mutator with inputs to all elements linked from an element"() {
         def creatorAction = Mock(Action)
         def mutatorAction = Mock(BiAction)
@@ -314,6 +408,35 @@ class DefaultModelRegistryTest extends Specification {
             node.addLink(registry.instanceCreator("parent.foo", "ignore me"))
             node.addLink(registry.instanceCreator("parent.bar", new Bean(value: "bar")))
         }
+        registry.createInstance("other", new Bean(value: "ignore me"))
+        mutatorAction.execute(_) >> { Bean bean -> bean.value = "prefix: $bean.value" }
+
+        registry.realize(ModelPath.path("parent"), ModelType.untyped()) // TODO - should not need this
+
+        expect:
+        registry.realize(ModelPath.path("parent.bar"), ModelType.of(Bean)).value == "prefix: bar"
+        registry.realize(ModelPath.path("parent.foo"), ModelType.of(String)) == "ignore me"
+
+        and:
+        registry.realize(ModelPath.path("other"), ModelType.of(Bean)).value == "ignore me"
+    }
+
+    def "can attach a mutator to all elements with specific type transitively linked from an element"() {
+        def creatorAction = Mock(Action)
+        def mutatorAction = Mock(Action)
+
+        given:
+        registry.create("parent") { it.unmanagedNode Integer, creatorAction }
+        creatorAction.execute(_) >> { MutableModelNode node ->
+            node.applyToAllLinksTransitive(ModelActionRole.Mutate, registry.action().type(Bean).action(mutatorAction))
+            node.addLink(registry.instanceCreator("parent.foo", "ignore me"))
+            node.addLink(registry.instanceCreator("parent.bar", new Bean(value: "bar")))
+            node.applyToLink(ModelActionRole.Mutate, registry.action().path("parent.bar").node { MutableModelNode bar ->
+                bar.addLink(registry.instanceCreator("parent.bar.child1", new Bean(value: "baz")))
+                bar.addLink(registry.instanceCreator("parent.bar.child2", "ignore me too"))
+            })
+        }
+        registry.createInstance("other", new Bean(value: "ignore me"))
         mutatorAction.execute(_) >> { Bean bean -> bean.value = "prefix: $bean.value" }
 
         registry.realize(ModelPath.path("parent"), ModelType.untyped()) // TODO - should not need this
@@ -321,6 +444,11 @@ class DefaultModelRegistryTest extends Specification {
         expect:
         registry.realize(ModelPath.path("parent.bar"), ModelType.of(Bean)).value == "prefix: bar"
         registry.realize(ModelPath.path("parent.foo"), ModelType.of(String)) == "ignore me"
+        registry.realize(ModelPath.path("parent.bar.child1"), ModelType.of(Bean)).value == "prefix: baz"
+        registry.realize(ModelPath.path("parent.bar.child2"), ModelType.of(String)) == "ignore me too"
+
+        and:
+        registry.realize(ModelPath.path("other"), ModelType.of(Bean)).value == "ignore me"
     }
 
     def "can attach a mutator with inputs to element linked from another element"() {
@@ -378,6 +506,18 @@ class DefaultModelRegistryTest extends Specification {
         e.cause.message == "Cannot set value for model element 'thing' as this element is not mutable."
     }
 
+    def "can replace an element that has not been used as input by a rule"() {
+        given:
+        registry.createInstance("thing", new Bean(value: "old"))
+        registry.configure(ModelActionRole.Mutate) { it.path("thing").action { it.value = "${it.value} path" } }
+        registry.configure(ModelActionRole.Mutate) { it.type(Bean).action { it.value = "${it.value} type" } }
+        registry.remove(ModelPath.path("thing"))
+        registry.createInstance("thing", new Bean(value: "new"))
+
+        expect:
+        registry.realize(ModelPath.path("thing"), ModelType.of(Bean)).value == "new path type"
+    }
+
     @Unroll
     def "cannot add action for #targetRole mutation when in #fromRole mutation"() {
         def action = Stub(Action)
@@ -391,8 +531,9 @@ class DefaultModelRegistryTest extends Specification {
         registry.realize(ModelPath.path("thing"), ModelType.untyped())
 
         then:
-        IllegalStateException e = thrown()
-        e.message.startsWith "Cannot add $targetRole rule 'X' for model element 'thing'"
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof IllegalStateException
+        e.cause.message == "Cannot add rule X for model element 'thing' at state ${targetRole.targetState.previous()} as this element is already at state ${fromRole.targetState.previous()}."
 
         where:
         fromRole                   | targetRole
@@ -427,8 +568,9 @@ class DefaultModelRegistryTest extends Specification {
         registry.realize(ModelPath.path("another"), ModelType.untyped())
 
         then:
-        IllegalStateException e = thrown()
-        e.message.startsWith "Cannot add $targetRole rule 'X' for model element 'thing'"
+        ModelRuleExecutionException e = thrown()
+        e.cause instanceof IllegalStateException
+        e.cause.message == "Cannot add rule X for model element 'thing' at state ${targetRole.targetState.previous()} as this element is already at state ${fromState}."
 
         where:
         fromState                       | targetRole
@@ -449,13 +591,12 @@ class DefaultModelRegistryTest extends Specification {
         ModelNode.State.SelfClosed      | ModelActionRole.Finalize
     }
 
-    @Unroll
     def "can add action for #targetRole mutation when in #fromRole mutation"() {
         given:
-        registry.createInstance("thing", new MutableValue(value: "initial")).configure(fromRole) {
+        registry.createInstance("thing", new Bean(value: "initial")).configure(fromRole) {
             it.path("thing").node { MutableModelNode node ->
                 registry.configure(targetRole) {
-                    it.path("thing").type(MutableValue).action {
+                    it.path("thing").type(Bean).action {
                         it.value = "mutated"
                     }
                 }
@@ -463,7 +604,7 @@ class DefaultModelRegistryTest extends Specification {
         }
 
         when:
-        def thing = registry.realize(ModelPath.path("thing"), ModelType.of(MutableValue))
+        def thing = registry.realize(ModelPath.path("thing"), ModelType.of(Bean))
 
         then:
         thing.value == "mutated"
@@ -487,7 +628,32 @@ class DefaultModelRegistryTest extends Specification {
         ModelActionRole.Validate   | ModelActionRole.Validate
     }
 
-    @Unroll
+    def "closes inputs for mutation discovered after running mutation with role #targetRole"() {
+        given:
+        registry.createInstance("thing", new Bean(value: "initial"))
+            .configure(targetRole) {
+                it.path("thing").node { MutableModelNode node ->
+                    registry.configure(targetRole) {
+                        it.path("thing").type(Bean).action("other", ModelType.of(Bean)) { subject, dep ->
+                            subject.value = dep.value
+                    }
+                }
+            }
+        }
+        // Include a dependency
+        registry.createInstance("other", new Bean())
+            .mutate { it.path("other").type(Bean).action { it.value = "input value"} }
+
+        when:
+        def thing = registry.realize(ModelPath.path("thing"), ModelType.of(Bean))
+
+        then:
+        thing.value == "input value"
+
+        where:
+        targetRole << ModelActionRole.values().find { it.targetState != null }
+    }
+
     def "can add action for #targetRole mutation when in #fromState state"() {
         def action = Stub(Action)
 
@@ -522,7 +688,6 @@ class DefaultModelRegistryTest extends Specification {
         ModelNode.State.Finalized       | ModelActionRole.Validate
     }
 
-    @Unroll
     def "can get node at state"() {
         given:
         registry.createInstance("thing", new Bean(value: "created"))
@@ -569,7 +734,6 @@ class DefaultModelRegistryTest extends Specification {
         events == ["created"]
     }
 
-    @Unroll
     def "asking for unknown element at any state returns null"() {
         expect:
         registry.atState(ModelPath.path("thing"), state) == null
@@ -581,15 +745,12 @@ class DefaultModelRegistryTest extends Specification {
     def "getting self closed collection defines all links but does not realise them until graph closed"() {
         given:
         def events = []
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
+        def mmType = ModelTypes.modelMap(Bean)
 
         registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) } as NamedEntityInstantiator).build())
-                .collection("things", Bean, iRef)
+                .modelMap("things", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
                 .mutate {
-            it.path "things" type cbType action { c ->
+            it.path "things" type mmType action { c ->
                 events << "collection mutated"
                 c.create("c1") { events << "$it.name created" }
             }
@@ -609,7 +770,6 @@ class DefaultModelRegistryTest extends Specification {
         events == ["collection mutated", "c1 created"]
     }
 
-    @Unroll
     def "cannot request model node at earlier state when at #state"() {
         given:
         registry.createInstance("thing", new Bean())
@@ -635,7 +795,6 @@ class DefaultModelRegistryTest extends Specification {
         state << ModelNode.State.values().toList()
     }
 
-    @Unroll
     def "is benign to request element at current state"() {
         given:
         registry.createInstance("thing", new Bean())
@@ -653,7 +812,6 @@ class DefaultModelRegistryTest extends Specification {
         state << ModelNode.State.values().toList()
     }
 
-    @Unroll
     def "is benign to request element at prior state"() {
         given:
         registry.createInstance("thing", new Bean())
@@ -671,7 +829,6 @@ class DefaultModelRegistryTest extends Specification {
         state << ModelNode.State.values().toList()
     }
 
-    @Unroll
     def "requesting at current state does not reinvoke actions"() {
         given:
         def events = []
@@ -703,11 +860,84 @@ class DefaultModelRegistryTest extends Specification {
         ModelNode.State.GraphClosed     | ModelActionRole.Validate
     }
 
+    def "reports unbound subjects"() {
+        given:
+        registry.mutate { it.path("a.b").descriptor("by-path").action() {} }
+        registry.mutate { it.type(Long).descriptor("by-type").action() {} }
+        registry.mutate { it.path("missing").type(String).descriptor("by-path-and-type").action() {} }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        normaliseLineSeparators(e.message) == '''The following model rules are unbound:
+  by-path
+    Mutable:
+      - a.b (java.lang.Object)
+  by-path-and-type
+    Mutable:
+      - missing (java.lang.String)
+  by-type
+    Mutable:
+      - <unspecified> (java.lang.Long)'''
+    }
+
+    def "reports unbound inputs"() {
+        given:
+        registry.create("foo") { it.descriptor("creator").unmanaged(Long, "a.b") {} }
+        registry.mutate { it.path("foo").descriptor("by-path").action(ModelPath.path("other.thing"), ModelType.of(String)) {} }
+        registry.mutate { it.type(Runnable).descriptor("by-type").action(String) {} }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        UnboundModelRulesException e = thrown()
+        normaliseLineSeparators(e.message) == '''The following model rules are unbound:
+  by-path
+    Mutable:
+      + foo (java.lang.Object)
+    Immutable:
+      - other.thing (java.lang.String) java.lang.String
+  by-type
+    Mutable:
+      - <unspecified> (java.lang.Runnable)
+    Immutable:
+      - <unspecified> (java.lang.String) java.lang.String
+  creator
+    Immutable:
+      - a.b (java.lang.Object) a.b'''
+    }
+
+    def "closes elements as required to bind all subjects and inputs"() {
+        given:
+        registry.mutate { it.path("a.1.2").action(ModelPath.path("b.1.2"), ModelType.of(String)) {} }
+        registry.create("a") { it.unmanaged("a") }
+        registry.mutate { it.path("a").node {
+            it.addLink(registry.creator("a.1").unmanaged("a.1"))
+            it.applyToLink(ModelActionRole.Finalize, registry.action().path("a.1").node {
+                it.addLink(registry.creator("a.1.2").unmanaged("a.1.2"))
+            })
+        } }
+        registry.create("b") { it.unmanaged("b") }
+        registry.mutate { it.path("b").node {
+            it.addLink(registry.creator("b.1").unmanaged("b.1"))
+            it.applyToLink(ModelActionRole.Finalize, registry.action().path("b.1").node {
+                it.addLink(registry.creator("b.1.2").unmanaged("b.1.2"))
+            })
+        } }
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
     def "only rules that actually have unbound inputs are reported as unbound"() {
         given:
-        def cbType = DefaultCollectionBuilder.typeOf(ModelType.of(Bean))
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Bean)
-        def iRef = ModelReference.of("instantiator", iType)
+        def mmType = ModelTypes.modelMap(Bean)
 
         registry
                 .createInstance("foo", new Bean())
@@ -718,14 +948,13 @@ class DefaultModelRegistryTest extends Specification {
             it.descriptor("bindable").path("foo").type(Bean).action("beans.element", ModelType.of(Bean)) {
             }
         }
-        .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Bean(name: name) } as NamedEntityInstantiator).build())
-                .collection("beans", Bean, iRef)
+                .modelMap("beans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
                 .mutate {
-            it.path "beans" type cbType action { c ->
+            it.path "beans" type mmType action { c ->
                 c.create("element")
             }
         }
-        .collection("emptyBeans", Bean, iRef)
+        .modelMap("emptyBeans", Bean) { it.registerFactory(Bean) { new Bean(name: it) } }
 
         when:
         registry.bindAllReferences()
@@ -740,6 +969,18 @@ class DefaultModelRegistryTest extends Specification {
       - emptyBeans.element (org.gradle.model.internal.registry.DefaultModelRegistryTest$Bean)'''
     }
 
+    def "does not report unbound creators of removed nodes"() {
+        given:
+        registry.create(ModelPath.path("unused")) { it.unmanaged(String, "unknown") { }}
+        registry.remove(ModelPath.path("unused"))
+
+        when:
+        registry.bindAllReferences()
+
+        then:
+        noExceptionThrown()
+    }
+
     def "two element mutation rule based configuration cycles are detected"() {
         given:
         registry.createInstance("foo", "foo")
@@ -753,9 +994,11 @@ class DefaultModelRegistryTest extends Specification {
         then:
         ConfigurationCycleException e = thrown()
         e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
-foo mutator parameter 1 (path: bar)
-  \\--- bar mutator (path: foo)
-    \\--- foo mutator""")
+foo
+\\- foo mutator
+   \\- bar
+      \\- bar mutator
+         \\- foo""")
     }
 
     def "multiple element configuration cycles are detected"() {
@@ -772,11 +1015,15 @@ foo mutator parameter 1 (path: bar)
         then:
         ConfigurationCycleException e = thrown()
         e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
-foo creator bar (path: bar)
-  \\--- bar creator fizz (path: fizz)
-    \\--- fizz mutator buzz (path: buzz)
-      \\--- buzz mutator foo (path: foo)
-        \\--- foo creator""")
+foo
+\\- foo creator
+   \\- bar
+      \\- bar creator
+         \\- fizz
+            \\- fizz mutator
+               \\- buzz
+                  \\- buzz mutator
+                     \\- foo""")
     }
 
     def "one element configuration cycles are detected"() {
@@ -790,18 +1037,19 @@ foo creator bar (path: bar)
         then:
         ConfigurationCycleException e = thrown()
         e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
-foo mutator java.lang.String (path: foo)
-  \\--- foo mutator""")
+foo
+\\- foo mutator
+   \\- foo""")
     }
 
     def "only the elements actually forming the cycle are reported when configuration cycles are detected"() {
         given:
-        registry.create("foo") { it.unmanaged(String, "bar") { "foo" } }
+        registry.create("foo") { it.unmanaged(Long, "bar") { 12 } }
                 .create("bar") { it.unmanaged(String, "fizz") { "bar" } }
-                .mutate { it.path("foo").type(String).action(String) {} }
-                .create("fizz") { it.unmanaged(String, "buzz") { "buzz" } }
-                .mutate { it.path("fizz").descriptor("fizz mutator").type(String).action("bar", ModelType.of(String), {}) }
-                .createInstance("buzz", "buzz")
+                .mutate { it.path("foo").action(String) {} }
+                .create("fizz") { it.unmanaged(Boolean, "buzz") { "buzz" } }
+                .mutate { it.path("fizz").descriptor("fizz mutator").action("bar", ModelType.of(String), {}) }
+                .createInstance("buzz", Long)
 
         when:
         registry.get("foo")
@@ -809,9 +1057,49 @@ foo mutator java.lang.String (path: foo)
         then:
         ConfigurationCycleException e = thrown()
         e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
-bar creator fizz (path: fizz)
-  \\--- fizz mutator bar (path: bar)
-    \\--- bar creator""")
+bar
+\\- bar creator
+   \\- fizz
+      \\- fizz mutator
+         \\- bar""")
+    }
+
+    def "implicit cycle when node depends on parent is detected"() {
+        given:
+        registry.createInstance("foo", "foo")
+                .mutate { it.path("foo").descriptor("foo mutator").node { it.addLink(registry.creator("foo.bar").unmanaged(Number, 12))} }
+                .mutate { it.path("foo.bar").descriptor("bar mutator").action(String) {} }
+
+        when:
+        registry.get("foo")
+
+        then:
+        ConfigurationCycleException e = thrown()
+        e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
+foo
+\\- foo.bar
+   \\- bar mutator
+      \\- foo""")
+    }
+
+    def "implicit cycle when node depends on ancestor is detected"() {
+        given:
+        registry.createInstance("foo", "foo")
+                .mutate { it.path("foo").descriptor("foo mutator").node { it.addLink(registry.creator("foo.bar").unmanaged(Number, 12))} }
+                .mutate { it.path("foo.bar").descriptor("bar mutator").node { it.addLink(registry.creator("foo.bar.baz").unmanaged(Number, 107))} }
+                .mutate { it.path("foo.bar.baz").descriptor("baz mutator").action(ModelType.of(String)) {} }
+
+        when:
+        registry.get("foo")
+
+        then:
+        ConfigurationCycleException e = thrown()
+        e.message == TextUtil.toPlatformLineSeparators("""A cycle has been detected in model rule dependencies. References forming the cycle:
+foo
+\\- foo.bar
+   \\- foo.bar.baz
+      \\- baz mutator
+         \\- foo""")
     }
 
     class Bean {
@@ -819,8 +1107,5 @@ bar creator fizz (path: fizz)
         String value
     }
 
-    class MutableValue {
-        String value
-    }
 
 }
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/HasDependencies.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/HasDependencies.groovy
new file mode 100644
index 0000000..67dd0f3
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/HasDependencies.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry
+
+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)
+ at interface HasDependencies {
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelGraphTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelGraphTest.groovy
index 8128d02..b82e452 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelGraphTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelGraphTest.groovy
@@ -111,7 +111,7 @@ class ModelGraphTest extends Specification {
         def c = node("c")
 
         given:
-        listener.matchPath() >> b.path
+        listener.getPath() >> b.path
         graph.addListener(listener)
 
         when:
@@ -132,7 +132,7 @@ class ModelGraphTest extends Specification {
         def c = node("c")
 
         given:
-        listener.matchPath() >> b.path
+        listener.getPath() >> b.path
         graph.add(a)
         graph.add(b)
         graph.add(c)
@@ -154,7 +154,7 @@ class ModelGraphTest extends Specification {
         def d = node("d")
 
         given:
-        listener.matchParent() >> a.path
+        listener.getParent() >> a.path
         a.links >> [b]
 
         when:
@@ -184,7 +184,7 @@ class ModelGraphTest extends Specification {
         def d = node("d", Long)
 
         given:
-        listener.matchType() >> ModelType.of(String)
+        listener.getType() >> ModelType.of(String)
 
         when:
         graph.add(a)
@@ -208,8 +208,8 @@ class ModelGraphTest extends Specification {
         def d = node("a.d", Long)
 
         given:
-        listener.matchType() >> ModelType.of(String)
-        listener.matchParent() >> a.path
+        listener.getType() >> ModelType.of(String)
+        listener.getParent() >> a.path
         a.links >> [b]
 
         when:
@@ -230,19 +230,19 @@ class ModelGraphTest extends Specification {
         0 * listener.onCreate(_)
     }
 
-    def "notifies listener about a node with matching scope and its children"() {
+    def "notifies listener of node with matching ancestor"() {
         def listener = Mock(ModelCreationListener)
 
-        def a = node("a", String)
-        def b = node("a.b", String)
-        def c = node("a.b.c", String)
-        def d = node("a.b.d", String)
-        def e = node("a.b.e", Integer)
-        def f = node("a.b.c.f", String)
+        def a = node("a")
+        def b = node("a.b")
+        def c = node("a.b.c")
+        def d = node("a.b.d")
+        def e = node("a.b.c.e")
+        def f = node("d")
 
         given:
-        listener.matchType() >> ModelType.of(String)
-        listener.matchScope() >> b.path
+        listener.ancestor >> a.path
+        a.links >> [b]
         b.links >> [c]
 
         when:
@@ -263,7 +263,39 @@ class ModelGraphTest extends Specification {
 
         then:
         1 * listener.onCreate(d)
+        1 * listener.onCreate(e)
         0 * listener.onCreate(_)
+    }
+
+    def "notifies listener of node with root ancestor"() {
+        def listener = Mock(ModelCreationListener)
+
+        def a = node("a")
+        def b = node("a.b")
+        def c = node("a.b.c")
+        def d = node("d")
+
+        given:
+        listener.ancestor >> ModelPath.ROOT
+
+        when:
+        graph.add(a)
+        graph.add(b)
+        graph.addListener(listener)
+
+        then:
+        1 * listener.onCreate(graph.root)
+        1 * listener.onCreate(a)
+        1 * listener.onCreate(b)
+        0 * listener.onCreate(_)
+
+        when:
+        graph.add(c)
+        graph.add(d)
+
+        then:
+        1 * listener.onCreate(c)
+        1 * listener.onCreate(d)
         0 * listener.onCreate(_)
     }
 
@@ -275,8 +307,8 @@ class ModelGraphTest extends Specification {
         def b = node("b")
 
         given:
-        listener2.matchPath() >> b.path
-        listener3.matchPath() >> b.path
+        listener2.getPath() >> b.path
+        listener3.getPath() >> b.path
 
         when:
         graph.add(a)
@@ -304,7 +336,7 @@ class ModelGraphTest extends Specification {
 
         given:
         graph.addListener(listener1)
-        listener2.matchPath() >> b.path
+        listener2.getPath() >> b.path
         graph.addListener(listener2)
 
         when:
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelRegistryEphemeralNodeTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelRegistryEphemeralNodeTest.groovy
index a7bb34b..ca27f2d 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelRegistryEphemeralNodeTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ModelRegistryEphemeralNodeTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.model.internal.registry
 
 import org.gradle.api.Transformer
+import org.gradle.api.internal.rules.ModelMapCreators
 import org.gradle.internal.Factory
 import org.gradle.model.internal.core.*
 import org.gradle.model.internal.fixture.ModelRegistryHelper
@@ -26,6 +27,29 @@ class ModelRegistryEphemeralNodeTest extends Specification {
 
     def registry = new ModelRegistryHelper()
 
+    def "non-ephemeral model nodes are reused when registry is reset"() {
+        when:
+        def events = []
+        registry.create("foo") { it.unmanaged(List, { [] } as Factory) }
+        registry.mutate(List) {
+            it.add "1"
+            events.add "mutate"
+        }
+
+        then:
+        registry.get("foo") == ["1"]
+        registry.node("foo").state == ModelNode.State.GraphClosed
+        events.size() == 1
+
+        when:
+        registry.prepareForReuse()
+
+        then:
+        registry.node("foo").state == ModelNode.State.GraphClosed
+        registry.get("foo") == ["1"]
+        events.size() == 1
+    }
+
     def "ephemeral model nodes are discarded when registry is reset"() {
         when:
         def events = []
@@ -84,6 +108,39 @@ class ModelRegistryEphemeralNodeTest extends Specification {
         events.size() == 4
     }
 
+    def "creator inputs for replaced ephemeral nodes are bound"() {
+        when:
+        registry.createOrReplace(registry.creator("foo") { it.ephemeral(true).unmanaged(List, ["old"])})
+        registry.createOrReplace(registry.creator("bar") { it.ephemeral(true).unmanaged(StringBuilder, List) { List l -> new StringBuilder(l[0]) }})
+        registry.mutate(List) {
+            it.add "2"
+        }
+        registry.mutate {
+            it.path("bar").type(StringBuilder).action(List) { bar, foo ->
+                bar.append " bar"
+            }
+        }
+
+        then:
+        registry.get("bar").toString() == "old bar"
+        registry.get("foo") == ["old", "2"]
+        registry.node("foo").state == ModelNode.State.GraphClosed
+        registry.node("bar").state == ModelNode.State.GraphClosed
+
+        when:
+        registry.prepareForReuse()
+        registry.createOrReplace(registry.creator("foo") { it.ephemeral(true).unmanaged(List, ["new"])})
+        registry.createOrReplace(registry.creator("bar") { it.ephemeral(true).unmanaged(StringBuilder, List) { List l -> new StringBuilder(l[0]) }})
+
+        then:
+        registry.node("foo").state == ModelNode.State.Known
+        registry.node("bar").state == ModelNode.State.Known
+        registry.get("foo") == ["new", "2"]
+
+        registry.node("bar").state == ModelNode.State.Known
+        registry.get("bar").toString() == "new bar"
+    }
+
     static class Thing {
         String name
         String value
@@ -91,15 +148,14 @@ class ModelRegistryEphemeralNodeTest extends Specification {
 
     def "children of ephemeral collection nodes are implicitly ephemeral"() {
         when:
-        def iType = DefaultCollectionBuilder.instantiatorTypeOf(Thing)
-        def iRef = ModelReference.of("instantiator", iType)
-
         registry
-                .create(ModelCreators.bridgedInstance(iRef, { name, type -> new Thing(name: name) } as NamedEntityInstantiator).build())
                 .create("things") {
-            it.ephemeral(true).collection(Thing, iRef)
+            it.ephemeral(true).modelMap(Thing)
+        }
+        .mutate(ModelMapCreators.instantiatorType(Thing)) {
+            it.registerFactory(Thing) { new Thing(name: it) }
         }
-        registry.mutateCollection("things", Thing) {
+        registry.mutateModelMap("things", Thing) {
             it.create("foo") {
                 it.value = "1"
             }
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RegistrySpec.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RegistrySpec.groovy
new file mode 100644
index 0000000..43e3309
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RegistrySpec.groovy
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry
+
+import org.gradle.api.Nullable
+import org.gradle.internal.BiActions
+import org.gradle.model.RuleSource
+import org.gradle.model.internal.core.*
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import org.gradle.model.internal.type.ModelType
+import spock.lang.Specification
+
+class RegistrySpec extends Specification {
+    protected static class TestNode extends ModelNodeInternal {
+        def links = []
+
+        TestNode(String creationPath, Class<?> type) {
+            super(toBinder(creationPath, type))
+        }
+
+        private static CreatorRuleBinder toBinder(String creationPath, Class<?> type) {
+            def creator = ModelCreators.of(ModelPath.path(creationPath), BiActions.doNothing()).descriptor("test").withProjection(new UnmanagedModelProjection(ModelType.of(type))).build()
+            def subject = new BindingPredicate()
+            def binder = new CreatorRuleBinder(creator, subject, [], [])
+            binder
+        }
+
+        @Override
+        ModelNodeInternal getTarget() {
+            return this
+        }
+
+        @Override
+        Iterable<? extends ModelNodeInternal> getLinks() {
+            return links
+        }
+
+        @Override
+        ModelNodeInternal addLink(ModelNodeInternal node) {
+            links << node
+            return node
+        }
+
+        @Override
+        def <T> ModelView<? extends T> asWritable(ModelType<T> type, ModelRuleDescriptor ruleDescriptor, List<ModelView<?>> implicitDependencies) {
+            return null
+        }
+
+        @Override
+        void addReference(ModelCreator creator) {
+
+        }
+
+        @Override
+        void addLink(ModelCreator creator) {
+
+        }
+
+        @Override
+        void removeLink(String name) {
+
+        }
+
+        @Override
+        def <T> void applyToSelf(ModelActionRole type, ModelAction<T> action) {
+
+        }
+
+        @Override
+        def <T> void applyToAllLinks(ModelActionRole type, ModelAction<T> action) {
+
+        }
+
+        @Override
+        def <T> void applyToAllLinksTransitive(ModelActionRole type, ModelAction<T> action) {
+
+        }
+
+        @Override
+        def <T> void applyToLink(ModelActionRole type, ModelAction<T> action) {
+
+        }
+
+        @Override
+        void applyToLinks(ModelType<?> type, Class<? extends RuleSource> rules) {
+
+        }
+
+        @Override
+        void applyToAllLinksTransitive(ModelType<?> type, Class<? extends RuleSource> rules) {
+
+        }
+
+        @Override
+        void applyToLink(String name, Class<? extends RuleSource> rules) {
+
+        }
+
+        @Override
+        void applyToSelf(Class<? extends RuleSource> rules) {
+
+        }
+
+        @Override
+        MutableModelNode getLink(String name) {
+            return null
+        }
+
+        @Override
+        int getLinkCount(ModelType<?> type) {
+            return 0
+        }
+
+        @Override
+        Set<String> getLinkNames(ModelType<?> type) {
+            return null
+        }
+
+        @Override
+        Iterable<? extends MutableModelNode> getLinks(ModelType<?> type) {
+            return null
+        }
+
+        @Override
+        int getLinkCount() {
+            return 0
+        }
+
+        @Override
+        boolean hasLink(String name) {
+            return false
+        }
+
+        @Override
+        boolean hasLink(String name, ModelType<?> type) {
+            return false
+        }
+
+        @Override
+        def <T> void setPrivateData(ModelType<? super T> type, T object) {
+
+        }
+
+        @Override
+        def <T> void setPrivateData(Class<? super T> type, T object) {
+
+        }
+
+        @Override
+        def <T> T getPrivateData(ModelType<T> type) {
+            return null
+        }
+
+        @Override
+        def <T> T getPrivateData(Class<T> type) {
+            return null
+        }
+
+        @Override
+        Object getPrivateData() {
+            return null
+        }
+
+        @Override
+        void setTarget(ModelNode target) {
+
+        }
+
+        @Override
+        void ensureUsable() {
+
+        }
+
+        @Override
+        void realize() {
+
+        }
+
+        @Override
+        def <T> ModelView<? extends T> asReadOnly(ModelType<T> type, @Nullable ModelRuleDescriptor ruleDescriptor) {
+            return null
+        }
+
+        @Override
+        MutableModelNode getParent() {
+            return null
+        }
+    }
+
+    protected static class RuleBinderTestBuilder {
+
+        private ModelRuleDescriptor descriptor
+        private BindingPredicate subjectReference
+        private String subjectReferenceBindingPath
+        private List<BindingPredicate> inputReferences = []
+        private Map<Integer, String> boundInputReferencePaths = [:]
+
+        void subjectReference(ModelReference<?> reference) {
+            subjectReference = new BindingPredicate(reference)
+        }
+
+        void subjectReference(Class type) {
+            subjectReference(ModelReference.of(ModelType.of(type)))
+        }
+
+        void subjectReference(String path, Class type) {
+            subjectReference(ModelReference.of(new ModelPath(path), ModelType.of(type)))
+        }
+
+        void bindSubjectReference(String path) {
+            subjectReferenceBindingPath = path
+        }
+
+        void inputReference(ModelReference<?> reference) {
+            inputReferences.add(new BindingPredicate(reference))
+        }
+
+        void inputReference(Class type) {
+            inputReference(ModelReference.of(ModelType.of(type)).inScope(ModelPath.ROOT))
+        }
+
+        void inputReference(Class type, ModelNode.State state) {
+            inputReference(ModelReference.of(null, ModelType.of(type), state).inScope(ModelPath.ROOT))
+        }
+
+        void inputReference(String path, ModelNode.State state) {
+            inputReference(ModelReference.of(ModelPath.path(path), ModelType.untyped(), state))
+        }
+
+        void inputReference(String path, Class type) {
+            inputReference(ModelReference.of(new ModelPath(path), ModelType.of(type)))
+        }
+
+        void bindInputReference(int index, String path) {
+            boundInputReferencePaths[index] = path
+        }
+
+        void descriptor(String descriptor) {
+            this.descriptor = new SimpleModelRuleDescriptor(descriptor)
+        }
+
+        RuleBinder build() {
+            def binder
+            if (subjectReference == null) {
+                def action = new ProjectionBackedModelCreator(null, descriptor, false, false, [], null, null)
+                binder = new CreatorRuleBinder(action, new BindingPredicate(), inputReferences, [])
+            } else {
+                def action = NoInputsModelAction.of(subjectReference.reference, descriptor, {})
+                binder = new MutatorRuleBinder<?>(subjectReference, inputReferences, action, [])
+            }
+            if (subjectReferenceBindingPath) {
+                binder.subjectBinding.boundTo = new TestNode(subjectReferenceBindingPath, Object)
+            }
+            boundInputReferencePaths.each { index, path ->
+                binder.inputBindings[index].boundTo = new TestNode(path, Object)
+            }
+            return binder
+        }
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RuleBindingsTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RuleBindingsTest.groovy
new file mode 100644
index 0000000..648065e
--- /dev/null
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/RuleBindingsTest.groovy
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.registry
+
+import org.gradle.api.Action
+import org.gradle.model.InvalidModelRuleException
+import org.gradle.model.ModelRuleBindingException
+import org.gradle.model.internal.core.ModelNode
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.type.ModelType
+import org.gradle.util.TextUtil
+
+class RuleBindingsTest extends RegistrySpec {
+    final ModelGraph graph = new ModelGraph(node(""))
+    final RuleBindings bindings = new RuleBindings(graph)
+
+    def "locates the subject of a rule by-path"() {
+        given:
+        def node = node("a")
+        def rule = rule("a", ModelNode.State.Mutated)
+        addNode(node)
+        bindings.add(rule)
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Mutated)) as List == [rule]
+        rule.subjectBinding.boundTo == node
+    }
+
+    def "locates the subject of a rule by-path when subject added after rule"() {
+        given:
+        def node = node("a")
+        def rule = rule("a", ModelNode.State.Mutated)
+        bindings.add(rule)
+        addNode(node)
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Mutated)) as List == [rule]
+        rule.subjectBinding.boundTo == node
+    }
+
+    def "locates the subject of a rule by-type"() {
+        given:
+        def node = node("a", Long)
+        def rule = rule(Long, ModelNode.State.Mutated)
+        addNode(node)
+        bindings.add(rule)
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Mutated)) as List == [rule]
+        rule.subjectBinding.boundTo == node
+    }
+
+    def "locates the subject of a rule by-type when subject added after rule"() {
+        given:
+        def node = node("a", Long)
+        def rule = rule(Long, ModelNode.State.Mutated)
+        bindings.add(rule)
+        addNode(node)
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Mutated)) as List == [rule]
+        rule.subjectBinding.boundTo == node
+    }
+
+    def "locates dependents of a node by-path"() {
+        given:
+        def node = node("a")
+        def rule = rule("other") { it.inputReference("a", ModelNode.State.Created) }
+        bindings.add(rule)
+        addNode(node)
+
+        expect:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Created)) as List == [rule]
+        rule.inputBindings[0].boundTo == node
+    }
+
+    def "locates dependents of a node by-type"() {
+        given:
+        def node = node("a", Long)
+        def rule = rule("other") { it.inputReference(Long, ModelNode.State.Created) }
+        bindings.add(rule)
+        addNode(node)
+
+        expect:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Created)) as List == [rule]
+        rule.inputBindings[0].boundTo == node
+    }
+
+    def "returns empty list when no rules with matching subject"() {
+        given:
+        bindings.add(rule("other", ModelNode.State.Created))
+        bindings.add(rule(Long, ModelNode.State.Created))
+        bindings.add(rule("path", ModelNode.State.Mutated))
+        bindings.add(rule(String, ModelNode.State.Mutated))
+        addNode(node("path", String))
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("path", ModelNode.State.Created)) as List == []
+    }
+
+    def "returns empty list when no rules with matching input"() {
+        given:
+        bindings.add(rule("other") { it.inputReference("other", ModelNode.State.Created) })
+        bindings.add(rule("other") { it.inputReference(Long, ModelNode.State.Created) })
+        bindings.add(rule("other") { it.inputReference(String, ModelNode.State.GraphClosed) })
+        bindings.add(rule("other") { it.inputReference("path", ModelNode.State.GraphClosed) })
+        addNode(node("path", String))
+
+        expect:
+        bindings.getRulesWithInput(nodeAtState("path", ModelNode.State.Created)) as List == []
+    }
+
+    def "returns rules with subject in fixed order - by-path in order added followed by by-type in order added"() {
+        def rule1 = rule("path", ModelNode.State.Finalized)
+        def rule2 = rule(Long, ModelNode.State.Finalized)
+        def rule3 = rule("path", ModelNode.State.Finalized)
+        def rule4 = rule(Long, ModelNode.State.Finalized)
+        def rule5 = rule(Long, ModelNode.State.Finalized)
+        def rule6 = rule("path", ModelNode.State.Finalized)
+
+        given:
+        bindings.add(rule1)
+        bindings.add(rule2)
+        bindings.add(rule3)
+        bindings.add(rule4)
+        bindings.add(rule5)
+        bindings.add(rule6)
+        addNode(node("path", Long))
+
+        expect:
+        bindings.getRulesWithSubject(nodeAtState("path", ModelNode.State.Finalized)) as List == [rule1, rule3, rule6, rule2, rule4, rule5]
+    }
+
+    def "returns rules with input in fixed order"() {
+        def rule1 = rule("other") { it.inputReference("path", ModelNode.State.Finalized) }
+        def rule2 = rule("other") { it.inputReference(Long, ModelNode.State.Finalized) }
+        def rule3 = rule("other") { it.inputReference("path", ModelNode.State.Finalized) }
+        def rule4 = rule("other") { it.inputReference(Long, ModelNode.State.Finalized) }
+        def rule5 = rule("other") { it.inputReference(Long, ModelNode.State.Finalized) }
+        def rule6 = rule("other") { it.inputReference("path", ModelNode.State.Finalized) }
+
+        given:
+        bindings.add(rule1)
+        bindings.add(rule2)
+        bindings.add(rule3)
+        bindings.add(rule4)
+        bindings.add(rule5)
+        bindings.add(rule6)
+        addNode(node("path", Long))
+
+        expect:
+        bindings.getRulesWithInput(nodeAtState("path", ModelNode.State.Finalized)) as List == [rule1, rule3, rule6, rule2, rule4, rule5]
+    }
+
+    def "includes rule once only when multiple inputs with same matching node"() {
+        def rule1 = rule("other") {
+            it.inputReference("path", ModelNode.State.Finalized)
+            it.inputReference(Long, ModelNode.State.Finalized)
+            it.inputReference("path", ModelNode.State.Finalized)
+        }
+        def rule2 = rule("other") {
+            it.inputReference(Long, ModelNode.State.Finalized)
+            it.inputReference("path", ModelNode.State.Finalized)
+        }
+
+        given:
+        bindings.add(rule1)
+        addNode(node("path", Long))
+        bindings.add(rule2)
+
+        expect:
+        bindings.getRulesWithInput(nodeAtState("path", ModelNode.State.Finalized)) as List == [rule1, rule2]
+        rule1.inputBindings.every { it.bound }
+        rule2.inputBindings.every { it.bound }
+    }
+
+    def "can replace by-path subject"() {
+        def node1 = node("a")
+        def node2 = node("a")
+        def rule = rule("a", ModelNode.State.Finalized)
+
+        given:
+        bindings.add(rule)
+
+        when:
+        addNode(node1)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.subjectBinding.boundTo == node1
+
+        when:
+        removeNode(node1)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == []
+        !rule.subjectBinding.bound
+
+        when:
+        addNode(node2)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.subjectBinding.boundTo == node2
+    }
+
+    def "can replace by-type subject"() {
+        def node1 = node("a", Long)
+        def node2 = node("a", Long)
+        def rule = rule(Long, ModelNode.State.Finalized)
+
+        given:
+        bindings.add(rule)
+
+        when:
+        addNode(node1)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.subjectBinding.boundTo == node1
+
+        when:
+        removeNode(node1)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == []
+        !rule.subjectBinding.bound
+
+        when:
+        addNode(node2)
+
+        then:
+        bindings.getRulesWithSubject(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.subjectBinding.boundTo == node2
+    }
+
+    def "can replace by-path input"() {
+        def node1 = node("a")
+        def node2 = node("a")
+        def rule = rule("other") { it.inputReference("a", ModelNode.State.Finalized) }
+
+        given:
+        bindings.add(rule)
+
+        when:
+        addNode(node1)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.inputBindings[0].boundTo == node1
+
+        when:
+        removeNode(node1)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == []
+        !rule.subjectBinding.bound
+
+        when:
+        addNode(node2)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.inputBindings[0].boundTo == node2
+    }
+
+    def "can replace by-type input"() {
+        def node1 = node("a", Long)
+        def node2 = node("a", Long)
+        def rule = rule("other") { it.inputReference(Long, ModelNode.State.Finalized) }
+
+        given:
+        bindings.add(rule)
+
+        when:
+        addNode(node1)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.inputBindings[0].boundTo == node1
+
+        when:
+        removeNode(node1)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == []
+        !rule.subjectBinding.bound
+
+        when:
+        addNode(node2)
+
+        then:
+        bindings.getRulesWithInput(nodeAtState("a", ModelNode.State.Finalized)) as List == [rule]
+        rule.inputBindings[0].boundTo == node2
+    }
+
+    def "cannot add node that would make a by-type subject ambiguous"() {
+        given:
+        bindings.add(rule(Long))
+        addNode(node("a", Long))
+
+        when:
+        addNode(node("b", Long))
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        TextUtil.normaliseLineSeparators(e.cause.message) == '''Type-only model reference of type java.lang.Long is ambiguous as multiple model elements are available for this type:
+  - a (created by: test)
+  - b (created by: test)'''
+    }
+
+    def "cannot add node that would make a by-type input ambiguous"() {
+        given:
+        bindings.add(rule("other") { it.inputReference(Long) })
+        addNode(node("a", Long))
+
+        when:
+        addNode(node("b", Long))
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        TextUtil.normaliseLineSeparators(e.cause.message) == '''Type-only model reference of type java.lang.Long is ambiguous as multiple model elements are available for this type:
+  - a (created by: test)
+  - b (created by: test)'''
+    }
+
+    def "cannot add rule with ambiguous by-type subject"() {
+        given:
+        addNode(node("a", Long))
+        addNode(node("b", Long))
+
+        when:
+        bindings.add(rule(Long))
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        TextUtil.normaliseLineSeparators(e.cause.message) == '''Type-only model reference of type java.lang.Long is ambiguous as multiple model elements are available for this type:
+  - a (created by: test)
+  - b (created by: test)'''
+    }
+
+    def "cannot add rule with ambiguous by-type input"() {
+        given:
+        addNode(node("a", Long))
+        addNode(node("b", Long))
+
+        when:
+        bindings.add(rule("other") { it.inputReference(Long) })
+
+        then:
+        InvalidModelRuleException e = thrown()
+        e.cause instanceof ModelRuleBindingException
+        TextUtil.normaliseLineSeparators(e.cause.message) == '''Type-only model reference of type java.lang.Long is ambiguous as multiple model elements are available for this type:
+  - a (created by: test)
+  - b (created by: test)'''
+    }
+
+    def "cannot add rule when subject state is not earlier than rule target state"() {
+        given:
+        def node = node("a", Long)
+        def rule = rule(Long, requiredState) { it.descriptor("<rule>") }
+        addNode(node)
+        node.state = currentState
+
+        when:
+        bindings.add(rule)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot add rule <rule> for model element 'a' at state ${requiredState.previous()} as this element is already at state $currentState."
+
+        where:
+        currentState                | requiredState
+        ModelNode.State.Initialized | ModelNode.State.Initialized
+        ModelNode.State.GraphClosed | ModelNode.State.Initialized
+    }
+
+    def "cannot add rule when input state is not at or earlier than input source state"() {
+        given:
+        def node = node("a", Long)
+        def rule = rule("other") {
+            it.descriptor("<rule>")
+            it.inputReference(Long, requiredState)
+        }
+
+        addNode(node)
+        node.state = currentState
+
+        when:
+        bindings.add(rule)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot add rule <rule> with input model element 'a' at state $requiredState as this element is already at state $currentState."
+
+        where:
+        currentState                | requiredState
+        ModelNode.State.Initialized | ModelNode.State.Initialized.previous()
+        ModelNode.State.Initialized | ModelNode.State.Created
+    }
+
+    NodeAtState nodeAtState(String path, ModelNode.State state) {
+        return new NodeAtState(ModelPath.path(path), state)
+    }
+
+    void addNode(TestNode node) {
+        graph.get(node.path.parent).addLink(node)
+        graph.add(node)
+        bindings.add(node)
+    }
+
+    void removeNode(TestNode node) {
+        graph.remove(node)
+        bindings.remove(node)
+    }
+
+    TestNode node(String path, Class type = Object) {
+        return new TestNode(path, type)
+    }
+
+    RuleBinder rule(Class subjectType, ModelNode.State targetState) {
+        def builder = new RuleBinderTestBuilder()
+        builder.subjectReference(ModelReference.of(null, ModelType.of(subjectType), targetState).inScope(ModelPath.ROOT))
+        builder.descriptor("rule with subject of type $subjectType.simpleName")
+        return builder.build()
+    }
+
+    RuleBinder rule(Class subjectType, ModelNode.State targetState, Action<? super RuleBinderTestBuilder> action) {
+        def builder = new RuleBinderTestBuilder()
+        builder.subjectReference(ModelReference.of(null, ModelType.of(subjectType), targetState).inScope(ModelPath.ROOT))
+        builder.descriptor("rule with subject of type $subjectType.simpleName")
+        action.execute(builder)
+        return builder.build()
+    }
+
+    RuleBinder rule(Class subjectType) {
+        def builder = new RuleBinderTestBuilder()
+        builder.subjectReference(ModelReference.of(null, ModelType.of(subjectType), ModelNode.State.Mutated).inScope(ModelPath.ROOT))
+        builder.descriptor("rule with subject of type $subjectType.simpleName")
+        return builder.build()
+    }
+
+    RuleBinder rule(String subjectPath) {
+        return rule(subjectPath, ModelNode.State.Mutated)
+    }
+
+    RuleBinder rule(String subjectPath, ModelNode.State targetState) {
+        def builder = new RuleBinderTestBuilder()
+        builder.subjectReference(ModelReference.of(ModelPath.path(subjectPath), ModelType.untyped(), targetState))
+        builder.descriptor("rule with subject $subjectPath")
+        return builder.build()
+    }
+
+    RuleBinder rule(String subjectPath, Action<? super RuleBinderTestBuilder> action) {
+        def builder = new RuleBinderTestBuilder()
+        builder.subjectReference(ModelReference.of(ModelPath.path(subjectPath), ModelType.untyped(), ModelNode.State.Mutated))
+        builder.descriptor("rule with subject $subjectPath")
+        action.execute(builder)
+        return builder.build()
+    }
+}
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ScopedRuleTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ScopedRuleTest.groovy
index 5973e14..8fd87de 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ScopedRuleTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/ScopedRuleTest.groovy
@@ -23,7 +23,6 @@ import org.gradle.model.Model
 import org.gradle.model.Mutate
 import org.gradle.model.Path
 import org.gradle.model.RuleSource
-import org.gradle.model.collection.internal.HasDependencies
 import org.gradle.model.internal.core.DependencyOnlyExtractedModelRule
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelRuleExecutionException
diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/UnboundRulesProcessorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/UnboundRulesProcessorTest.groovy
index 2f07b16..5139084 100644
--- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/UnboundRulesProcessorTest.groovy
+++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/registry/UnboundRulesProcessorTest.groovy
@@ -16,24 +16,18 @@
 
 package org.gradle.model.internal.registry
 
-import org.gradle.api.Nullable
 import org.gradle.api.Transformer
-import org.gradle.internal.BiActions
 import org.gradle.internal.Transformers
-import org.gradle.model.RuleSource
-import org.gradle.model.internal.core.*
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.core.ModelReference
 import org.gradle.model.internal.report.unbound.UnboundRule
 import org.gradle.model.internal.report.unbound.UnboundRuleInput
 import org.gradle.model.internal.report.unbound.UnboundRulesReporter
-import org.gradle.model.internal.type.ModelType
 import org.gradle.util.ConfigureUtil
-import spock.lang.Specification
 
 import static org.gradle.util.TextUtil.normaliseLineSeparators
 
-class UnboundRulesProcessorTest extends Specification {
+class UnboundRulesProcessorTest extends RegistrySpec {
 
     List<RuleBinder> binders = []
 
@@ -167,10 +161,9 @@ class UnboundRulesProcessorTest extends Specification {
     def "creates scoped unbound rules with by-type bound subject"() {
         binder {
             descriptor("ruleWithUnboundSubjectReference")
-            scope("some.scope")
-            subjectReference(String)
+            subjectReference(ModelReference.of(String).inScope(ModelPath.path("some.scope")))
             inputReference(String)
-            inputReference("input", Boolean)
+            inputReference(ModelReference.of(Boolean).inScope(ModelPath.path("other.scope")))
         }
 
         expect:
@@ -178,219 +171,9 @@ class UnboundRulesProcessorTest extends Specification {
                 UnboundRule.descriptor("ruleWithUnboundSubjectReference")
                         .mutableInput(UnboundRuleInput.type(String).scope("some.scope"))
                         .immutableInput(UnboundRuleInput.type(String))
-                        .immutableInput(UnboundRuleInput.type(Boolean).path("some.scope.input"))
+                        .immutableInput(UnboundRuleInput.type(Boolean).scope("other.scope"))
         )
     }
 
-    def "creates scoped unbound rules with by-path bound subject"() {
-        binder {
-            descriptor("ruleWithUnboundSubjectReference")
-            scope("some.scope")
-            subjectReference("subject", String)
-        }
-
-        expect:
-        reportForProcessedBinders == reportFor(
-                UnboundRule.descriptor("ruleWithUnboundSubjectReference")
-                        .mutableInput(UnboundRuleInput.type(String).path("some.scope.subject"))
-        )
-    }
-
-    private class RuleBinderTestBuilder {
-
-        private ModelRuleDescriptor descriptor
-        private ModelReference<?> subjectReference
-        private String subjectReferenceBindingPath
-        private List<ModelReference<?>> inputReferences = []
-        private Map<Integer, String> boundInputReferencePaths = [:]
-        private ModelPath scope = ModelPath.ROOT
-
-        void subjectReference(Class type) {
-            subjectReference = ModelReference.of(ModelType.of(type))
-        }
-
-        void subjectReference(String path, Class type) {
-            subjectReference = ModelReference.of(new ModelPath(path), ModelType.of(type))
-        }
-
-        void bindSubjectReference(String path) {
-            subjectReferenceBindingPath = path
-        }
-
-        void inputReference(Class type) {
-            inputReferences.add(ModelReference.of(ModelType.of(type)))
-        }
-
-        void inputReference(String path, Class type) {
-            inputReferences.add(ModelReference.of(new ModelPath(path), ModelType.of(type)))
-        }
-
-        void bindInputReference(int index, String path) {
-            boundInputReferencePaths[index] = path
-        }
-
-        void descriptor(String descriptor) {
-            this.descriptor = new SimpleModelRuleDescriptor(descriptor)
-        }
-
-        void scope(String path) {
-            scope = ModelPath.path(path)
-        }
-
-        RuleBinder build() {
-            def binder = new RuleBinder(inputReferences, descriptor, scope, []) {
-                @Override
-                ModelBinding<?> getSubjectBinding() {
-                    subjectReferenceBindingPath ? bind(getSubjectReference(), new TestNode(subjectReferenceBindingPath)) : null
-                }
-
-                @Override
-                ModelReference<?> getSubjectReference() {
-                    RuleBinderTestBuilder.this.subjectReference
-                }
-            }
-            boundInputReferencePaths.each { index, path ->
-                binder.bindInput(index, new TestNode(path))
-            }
-            return binder
-        }
-    }
-
-    private static class TestNode extends ModelNodeInternal {
-        TestNode(String creationPath) {
-            super(toBinder(creationPath))
-        }
-
-        private static CreatorRuleBinder toBinder(String creationPath) {
-            def creator = ModelCreators.of(ModelReference.of(creationPath), BiActions.doNothing()).descriptor("test").withProjection(EmptyModelProjection.INSTANCE).build()
-            def binder = new CreatorRuleBinder(creator, ModelPath.ROOT, [])
-            binder
-        }
-
-        @Override
-        ModelNodeInternal getTarget() {
-            return this
-        }
-
-        @Override
-        Iterable<? extends ModelNodeInternal> getLinks() {
-            return null
-        }
-
-        @Override
-        ModelNodeInternal addLink(ModelNodeInternal node) {
-            return null
-        }
-
-        @Override
-        def <T> ModelView<? extends T> asWritable(ModelType<T> type, ModelRuleDescriptor ruleDescriptor, List<ModelView<?>> implicitDependencies) {
-            return null
-        }
-
-        @Override
-        void addReference(ModelCreator creator) {
-
-        }
-
-        @Override
-        void addLink(ModelCreator creator) {
-
-        }
 
-        @Override
-        void removeLink(String name) {
-
-        }
-
-        @Override
-        def <T> void applyToSelf(ModelActionRole type, ModelAction<T> action) {
-
-        }
-
-        @Override
-        def <T> void applyToAllLinks(ModelActionRole type, ModelAction<T> action) {
-
-        }
-
-        @Override
-        def <T> void applyToLink(ModelActionRole type, ModelAction<T> action) {
-
-        }
-
-
-        @Override
-        def <T> void applyToLinks(Class<T> type, Class<? extends RuleSource> rules) {
-
-        }
-
-        @Override
-        void applyToLink(String name, Class<? extends RuleSource> rules) {
-
-        }
-
-        @Override
-        void applyToSelf(Class<? extends RuleSource> rules) {
-
-        }
-
-        @Override
-        MutableModelNode getLink(String name) {
-            return null
-        }
-
-        @Override
-        int getLinkCount(ModelType<?> type) {
-            return 0
-        }
-
-        @Override
-        Set<String> getLinkNames(ModelType<?> type) {
-            return null
-        }
-
-        @Override
-        Iterable<? extends MutableModelNode> getLinks(ModelType<?> type) {
-            return null
-        }
-
-        @Override
-        boolean hasLink(String name) {
-            return false
-        }
-
-        @Override
-        boolean hasLink(String name, ModelType<?> type) {
-            return false
-        }
-
-        @Override
-        def <T> void setPrivateData(ModelType<? super T> type, T object) {
-
-        }
-
-        @Override
-        def <T> T getPrivateData(ModelType<T> type) {
-            return null
-        }
-
-        @Override
-        Object getPrivateData() {
-            return null
-        }
-
-        @Override
-        void setTarget(ModelNode target) {
-
-        }
-
-        @Override
-        void ensureUsable() {
-
-        }
-
-        @Override
-        def <T> ModelView<? extends T> asReadOnly(ModelType<T> type, @Nullable ModelRuleDescriptor ruleDescriptor) {
-            return null
-        }
-    }
 }
diff --git a/subprojects/model-core/src/testFixtures/groovy/org/gradle/model/internal/fixture/ModelRegistryHelper.java b/subprojects/model-core/src/testFixtures/groovy/org/gradle/model/internal/fixture/ModelRegistryHelper.java
index 81baf15..d95132d 100644
--- a/subprojects/model-core/src/testFixtures/groovy/org/gradle/model/internal/fixture/ModelRegistryHelper.java
+++ b/subprojects/model-core/src/testFixtures/groovy/org/gradle/model/internal/fixture/ModelRegistryHelper.java
@@ -17,11 +17,15 @@
 package org.gradle.model.internal.fixture;
 
 import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectFactory;
 import org.gradle.api.Nullable;
 import org.gradle.api.Transformer;
+import org.gradle.api.internal.PolymorphicNamedEntityInstantiator;
+import org.gradle.api.internal.rules.ModelMapCreators;
+import org.gradle.api.internal.rules.RuleAwarePolymorphicNamedEntityInstantiator;
 import org.gradle.internal.*;
+import org.gradle.model.ModelMap;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
@@ -34,10 +38,12 @@ import org.gradle.model.internal.registry.ModelRegistry;
 import org.gradle.model.internal.registry.ModelRegistryScope;
 import org.gradle.model.internal.registry.UnboundModelRulesException;
 import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import static org.gradle.model.internal.core.ModelActionRole.Mutate;
 import static org.gradle.model.internal.core.ModelPath.nonNullValidatedPath;
@@ -63,6 +69,14 @@ public class ModelRegistryHelper implements ModelRegistry {
         this.modelRegistry = modelRegistry;
     }
 
+    public ModelRuleDescriptor desc(String p) {
+        return new SimpleModelRuleDescriptor(p);
+    }
+
+    public ModelPath path(String p) {
+        return ModelPath.path(p);
+    }
+
     @Override
     public <T> T realize(ModelPath path, ModelType<T> type) {
         return modelRegistry.realize(path, type);
@@ -70,18 +84,18 @@ public class ModelRegistryHelper implements ModelRegistry {
 
     @Override
     @Nullable
-    public ModelNode atState(ModelPath path, ModelNode.State state) {
-        return modelRegistry.atState(path, state);
+    public MutableModelNode atState(ModelPath path, ModelNode.State state) {
+        return (MutableModelNode) modelRegistry.atState(path, state);
     }
 
-    public ModelNode atState(String path, ModelNode.State state) {
+    public MutableModelNode atState(String path, ModelNode.State state) {
         return atState(ModelPath.path(path), state);
     }
 
     @Override
     @Nullable
-    public ModelNode atStateOrLater(ModelPath path, ModelNode.State state) {
-        return modelRegistry.atStateOrLater(path, state);
+    public MutableModelNode atStateOrLater(ModelPath path, ModelNode.State state) {
+        return (MutableModelNode) modelRegistry.atStateOrLater(path, state);
     }
 
     @Override
@@ -89,6 +103,10 @@ public class ModelRegistryHelper implements ModelRegistry {
         return modelRegistry.state(path);
     }
 
+    public ModelNode.State state(String path) {
+        return modelRegistry.state(ModelPath.path(path));
+    }
+
     @Override
     @Nullable
     public <T> T find(ModelPath path, ModelType<T> type) {
@@ -164,12 +182,17 @@ public class ModelRegistryHelper implements ModelRegistry {
     }
 
     @Override
-    public ModelNode node(ModelPath path) {
+    public MutableModelNode getRoot() {
+        return modelRegistry.getRoot();
+    }
+
+    @Override
+    public MutableModelNode node(ModelPath path) {
         return modelRegistry.node(path);
     }
 
     @Nullable
-    public ModelNode node(String path) {
+    public MutableModelNode node(String path) {
         return node(ModelPath.path(path));
     }
 
@@ -191,20 +214,42 @@ public class ModelRegistryHelper implements ModelRegistry {
         return creator(ModelPath.path(path));
     }
 
-    public <I> ModelRegistryHelper collection(String path, final Class<I> itemType, final ModelReference<? extends NamedEntityInstantiator<I>> instantiator) {
+    public <I> ModelRegistryHelper modelMap(String path, final Class<I> itemType, final Action<? super PolymorphicNamedEntityInstantiator<I>> registrations) {
+        configure(ModelActionRole.Initialize, ModelReference.of(path, ModelMapCreators.instantiatorType(itemType)), new Action<RuleAwarePolymorphicNamedEntityInstantiator<I>>() {
+            @Override
+            public void execute(final RuleAwarePolymorphicNamedEntityInstantiator<I> instantiator) {
+                registrations.execute(new PolymorphicNamedEntityInstantiator<I>() {
+                    @Override
+                    public Set<? extends Class<? extends I>> getCreatableTypes() {
+                        return instantiator.getCreatableTypes();
+                    }
+
+                    @Override
+                    public <U extends I> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+                        instantiator.registerFactory(type, factory, new SimpleModelRuleDescriptor("ModelRegistryHelper.modelMap"));
+                    }
+
+                    @Override
+                    public <S extends I> S create(String name, Class<S> type) {
+                        return instantiator.create(name, type);
+                    }
+                });
+            }
+        });
         return create(path, new Transformer<ModelCreator, ModelCreatorBuilder>() {
             @Override
             public ModelCreator transform(ModelCreatorBuilder modelCreatorBuilder) {
-                return modelCreatorBuilder.collection(itemType, instantiator);
+                return modelCreatorBuilder.modelMap(itemType);
             }
         });
+
     }
 
-    public <I> ModelRegistryHelper mutateCollection(final String path, final Class<I> itemType, final Action<? super CollectionBuilder<I>> action) {
+    public <I> ModelRegistryHelper mutateModelMap(final String path, final Class<I> itemType, final Action<? super ModelMap<I>> action) {
         return mutate(new Transformer<ModelAction<?>, ModelActionBuilder<Object>>() {
             @Override
             public ModelAction<?> transform(ModelActionBuilder<Object> builder) {
-                return builder.path(path).type(DefaultCollectionBuilder.typeOf(ModelType.of(itemType))).action(action);
+                return builder.path(path).type(ModelTypes.modelMap(itemType)).action(action);
             }
         });
     }
@@ -233,6 +278,10 @@ public class ModelRegistryHelper implements ModelRegistry {
         return apply(Mutate, type, action);
     }
 
+    public <T> ModelRegistryHelper mutate(ModelType<T> type, Action<? super T> action) {
+        return apply(Mutate, type, action);
+    }
+
     public <T> ModelRegistryHelper mutate(ModelReference<T> reference, Action<? super T> action) {
         return configure(Mutate, reference, action);
     }
@@ -256,6 +305,10 @@ public class ModelRegistryHelper implements ModelRegistry {
     }
 
     private <T> ModelRegistryHelper apply(ModelActionRole role, final Class<T> type, final Action<? super T> action) {
+        return apply(role, ModelType.of(type), action);
+    }
+
+    private <T> ModelRegistryHelper apply(ModelActionRole role, final ModelType<T> type, final Action<? super T> action) {
         return configure(role, ModelReference.of(type), action);
     }
 
@@ -284,20 +337,24 @@ public class ModelRegistryHelper implements ModelRegistry {
         modelRegistry.realize(nonNullValidatedPath(path), ModelType.UNTYPED);
     }
 
+    public <T> T realize(String path, Class<T> type) {
+        return modelRegistry.realize(nonNullValidatedPath(path), ModelType.of(type));
+    }
+
     public static <C> ModelCreator creator(String path, Class<C> type, String inputPath, final Transformer<? extends C, Object> action) {
         return creator(path, ModelType.of(type), inputPath, action);
     }
 
     public static <C> ModelCreator creator(String path, final ModelType<C> modelType, String inputPath, final Transformer<? extends C, Object> action) {
-        return ModelCreators.of(ModelReference.of(path, modelType), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+        return ModelCreators.of(ModelPath.path(path), new BiAction<MutableModelNode, List<ModelView<?>>>() {
             @Override
             public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
                 mutableModelNode.setPrivateData(modelType, action.transform(inputs.get(0).getInstance()));
             }
         }).withProjection(new UnmanagedModelProjection<C>(modelType, true, true))
-                .inputs(refs(ModelReference.of(inputPath)))
-                .descriptor("create " + path)
-                .build();
+            .inputs(refs(ModelReference.of(inputPath)))
+            .descriptor("create " + path)
+            .build();
     }
 
 
@@ -380,10 +437,14 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         public <I> ModelAction<T> action(final ModelPath modelPath, final ModelType<I> inputType, String referenceDescription, final BiAction<? super T, ? super I> action) {
-            return build(refs(ModelReference.of(modelPath, inputType, referenceDescription)), new TriAction<MutableModelNode, T, List<ModelView<?>>>() {
+            return action(ModelReference.of(modelPath, inputType, referenceDescription), action);
+        }
+
+        public <I> ModelAction<T> action(final ModelReference<I> inputReference, final BiAction<? super T, ? super I> action) {
+            return build(refs(inputReference), new TriAction<MutableModelNode, T, List<ModelView<?>>>() {
                 @Override
                 public void execute(MutableModelNode mutableModelNode, T t, List<ModelView<?>> inputs) {
-                    action.execute(t, ModelViews.assertType(inputs.get(0), inputType).getInstance());
+                    action.execute(t, ModelViews.assertType(inputs.get(0), inputReference.getType()).getInstance());
                 }
             });
         }
@@ -405,27 +466,12 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         private static <T> ModelAction<T> toAction(final List<ModelReference<?>> references, final TriAction<? super MutableModelNode, ? super T, ? super List<ModelView<?>>> action, final ModelPath path, final ModelType<T> type, final ModelRuleDescriptor descriptor) {
-            return new ModelAction<T>() {
-                @Override
-                public ModelReference<T> getSubject() {
-                    return ModelReference.of(path, type);
-                }
-
+            return DirectNodeInputUsingModelAction.of(ModelReference.of(path, type), descriptor, references, new TriAction<MutableModelNode, T, List<ModelView<?>>>() {
                 @Override
-                public void execute(MutableModelNode modelNode, T object, List<ModelView<?>> inputs) {
-                    action.execute(modelNode, object, inputs);
+                public void execute(MutableModelNode modelNode, T t, List<ModelView<?>> inputs) {
+                    action.execute(modelNode, t, inputs);
                 }
-
-                @Override
-                public List<ModelReference<?>> getInputs() {
-                    return references;
-                }
-
-                @Override
-                public ModelRuleDescriptor getDescriptor() {
-                    return descriptor;
-                }
-            };
+            });
         }
     }
 
@@ -466,16 +512,16 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         public <C> ModelCreator unmanaged(final ModelType<C> modelType, String inputPath, String inputDescriptor, final Transformer<? extends C, Object> action) {
-            return ModelCreators.of(ModelReference.of(path), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+            return ModelCreators.of(path, new BiAction<MutableModelNode, List<ModelView<?>>>() {
                 @Override
                 public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
                     mutableModelNode.setPrivateData(modelType, action.transform(inputs.get(0).getInstance()));
                 }
             }).withProjection(new UnmanagedModelProjection<C>(modelType, true, true))
-                    .inputs(ModelReference.of(inputPath, ModelType.UNTYPED, inputDescriptor))
-                    .descriptor(descriptor)
-                    .ephemeral(ephemeral)
-                    .build();
+                .inputs(ModelReference.of(inputPath, ModelType.UNTYPED, inputDescriptor))
+                .descriptor(descriptor)
+                .ephemeral(ephemeral)
+                .build();
         }
 
         public <C, I> ModelCreator unmanaged(Class<C> type, final Class<I> inputType, final Transformer<? extends C, ? super I> action) {
@@ -483,16 +529,16 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         public <C, I> ModelCreator unmanaged(final ModelType<C> modelType, final ModelType<I> inputModelType, final Transformer<? extends C, ? super I> action) {
-            return ModelCreators.of(ModelReference.of(path, modelType), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+            return ModelCreators.of(path, new BiAction<MutableModelNode, List<ModelView<?>>>() {
                 @Override
                 public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
                     mutableModelNode.setPrivateData(modelType, action.transform(ModelViews.assertType(inputs.get(0), inputModelType).getInstance()));
                 }
             }).withProjection(new UnmanagedModelProjection<C>(modelType, true, true))
-                    .inputs(ModelReference.of(inputModelType))
-                    .ephemeral(ephemeral)
-                    .descriptor(descriptor)
-                    .build();
+                .inputs(ModelReference.of(inputModelType))
+                .ephemeral(ephemeral)
+                .descriptor(descriptor)
+                .build();
         }
 
         public <C> ModelCreator unmanaged(Class<C> type, final Factory<? extends C> initializer) {
@@ -504,15 +550,15 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         private <C> ModelCreator unmanaged(final ModelType<C> modelType, final Factory<? extends C> initializer) {
-            return ModelCreators.of(ModelReference.of(path), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+            return ModelCreators.of(path, new BiAction<MutableModelNode, List<ModelView<?>>>() {
                 @Override
                 public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
                     mutableModelNode.setPrivateData(modelType, initializer.create());
                 }
             }).withProjection(new UnmanagedModelProjection<C>(modelType, true, true))
-                    .descriptor(descriptor)
-                    .ephemeral(ephemeral)
-                    .build();
+                .descriptor(descriptor)
+                .ephemeral(ephemeral)
+                .build();
         }
 
         public <C> ModelCreator unmanagedNode(Class<C> modelType, final Action<? super MutableModelNode> action) {
@@ -520,15 +566,15 @@ public class ModelRegistryHelper implements ModelRegistry {
         }
 
         public <C> ModelCreator unmanagedNode(ModelType<C> modelType, final Action<? super MutableModelNode> action) {
-            return ModelCreators.of(ModelReference.of(path), new BiAction<MutableModelNode, List<ModelView<?>>>() {
+            return ModelCreators.of(path, new BiAction<MutableModelNode, List<ModelView<?>>>() {
                 @Override
                 public void execute(MutableModelNode mutableModelNode, List<ModelView<?>> inputs) {
                     action.execute(mutableModelNode);
                 }
             }).withProjection(new UnmanagedModelProjection<C>(modelType, true, true))
-                    .descriptor(descriptor)
-                    .ephemeral(ephemeral)
-                    .build();
+                .descriptor(descriptor)
+                .ephemeral(ephemeral)
+                .build();
         }
 
         public <C> ModelCreator unmanaged(C c) {
@@ -545,23 +591,11 @@ public class ModelRegistryHelper implements ModelRegistry {
             });
         }
 
-        public <I> ModelCreator collection(Class<I> itemType, final ModelReference<? extends NamedEntityInstantiator<? super I>> instantiator) {
-            final ModelType<I> itemModelType = ModelType.of(itemType);
-            final ModelType<CollectionBuilder<I>> collectionBuilderType = DefaultCollectionBuilder.typeOf(itemModelType);
-
-            return ModelCreators.of(ModelReference.of(path, collectionBuilderType), new BiAction<MutableModelNode, List<ModelView<?>>>() {
-                @Override
-                public void execute(MutableModelNode node, List<ModelView<?>> inputs) {
-                    node.setPrivateData(
-                            collectionBuilderType,
-                            new DefaultCollectionBuilder<I>(itemModelType, descriptor, node, DefaultCollectionBuilder.createVia(instantiator))
-                    );
-                }
-            })
-                    .withProjection(new UnmanagedModelProjection<CollectionBuilder<I>>(collectionBuilderType, true, true))
-                    .descriptor(descriptor)
-                    .ephemeral(ephemeral)
-                    .build();
+        public <I> ModelCreator modelMap(Class<I> itemType) {
+            return ModelMapCreators.of(path, itemType)
+                .descriptor(descriptor)
+                .ephemeral(ephemeral)
+                .build();
         }
     }
 
diff --git a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslCreationIntegrationTest.groovy b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslCreationIntegrationTest.groovy
index 44744bb..4eca43d 100644
--- a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslCreationIntegrationTest.groovy
+++ b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslCreationIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.model.dsl
 
+import groovy.transform.NotYetImplemented
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.EnableModelDsl
 
@@ -27,11 +28,37 @@ class ModelDslCreationIntegrationTest extends AbstractIntegrationSpec {
         EnableModelDsl.enable(executer)
     }
 
-    def "can create elements"() {
+    def "can create and initialize elements"() {
         when:
         buildScript """
-            import org.gradle.model.*
+            @Managed
+            interface Thing {
+                String getName()
+                void setName(String name)
+            }
+
+            model {
+                thing1(Thing) {
+                    name = "foo"
+                }
+                tasks {
+                    create("echo") {
+                        doLast {
+                            println "thing1.name: " + \$("thing1.name")
+                        }
+                    }
+                }
+            }
+        """
+
+        then:
+        succeeds "echo"
+        output.contains "thing1.name: foo"
+    }
 
+    def "creator closure can reference inputs"() {
+        when:
+        buildScript """
             @Managed
             interface Thing {
                 String getName()
@@ -60,11 +87,41 @@ class ModelDslCreationIntegrationTest extends AbstractIntegrationSpec {
         output.contains "thing2.name: foo bar"
     }
 
-    def "can create elements without mutating"() {
+    @NotYetImplemented
+    def "creator closure can reference inputs using relative property reference"() {
         when:
         buildScript """
-            import org.gradle.model.*
+            @Managed
+            interface Thing {
+                String getName()
+                void setName(String name)
+            }
+
+            model {
+                thing1(Thing) {
+                    name = "foo"
+                }
+                thing2(Thing) {
+                    name = "\${thing1.name} bar" // reference in a gstring
+                }
+                tasks {
+                    create("echo") {
+                        doLast {
+                            println "thing2.name: " + thing2.name
+                        }
+                    }
+                }
+            }
+        """
 
+        then:
+        succeeds "echo"
+        output.contains "thing2.name: foo bar"
+    }
+
+    def "can create elements without mutating"() {
+        when:
+        buildScript """
             @Managed
             interface Thing {
                 String getName()
@@ -88,11 +145,46 @@ class ModelDslCreationIntegrationTest extends AbstractIntegrationSpec {
         output.contains "thing1.name: null"
     }
 
-    def "cannot create non managed types"() {
+    def "can apply defaults before creator closure is invoked"() {
         when:
         buildScript """
-            import org.gradle.model.*
+            @Managed
+            interface Thing {
+                String getName()
+                void setName(String name)
+            }
+
+            class MyPlugin extends RuleSource {
+                @Defaults
+                void applyDefaults(Thing thing) {
+                    thing.name = "default"
+                }
+            }
 
+            apply plugin: MyPlugin
+
+            model {
+                thing1(Thing) {
+                    name = "\$name foo"
+                }
+                tasks {
+                    create("echo") {
+                        doLast {
+                            println "thing1.name: " + \$("thing1.name")
+                        }
+                    }
+                }
+            }
+        """
+
+        then:
+        succeeds "echo"
+        output.contains "thing1.name: default foo"
+    }
+
+    def "cannot create non managed types"() {
+        when:
+        buildScript """
             interface Thing {
                 String getName()
                 void setName(String name)
diff --git a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslIntegrationTest.groovy b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslIntegrationTest.groovy
index e23105d..cb3c686 100644
--- a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslIntegrationTest.groovy
+++ b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/ModelDslIntegrationTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.model.dsl
 
+import groovy.transform.NotYetImplemented
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.model.dsl.internal.transform.RulesVisitor
@@ -33,13 +34,47 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
         EnableModelDsl.enable(executer)
     }
 
-    def "rule inputs can be referenced in closures that are not executed during rule execution"() {
+    @NotYetImplemented
+    def "can reference rule inputs using relative property reference"() {
         when:
         buildScript """
-            import org.gradle.model.*
+            class MyPlugin extends RuleSource {
+                @Model
+                String foo() {
+                  "foo"
+                }
 
-            class MyPlugin {
-              static class Rules extends RuleSource {
+                @Model
+                List<String> strings() {
+                  []
+                }
+            }
+
+            apply plugin: MyPlugin
+
+            model {
+                tasks {
+                    printStrings(Task) {
+                        doLast {
+                            println strings
+                        }
+                    }
+                }
+                strings {
+                    add(foo)
+                }
+            }
+"""
+
+        then:
+        succeeds "printStrings"
+        output.contains "strings: [foo]"
+    }
+
+    def "rule inputs can be referenced in closures that are not executed during rule execution"() {
+        when:
+        buildScript """
+            class MyPlugin extends RuleSource {
                 @Model
                 String foo() {
                   "foo"
@@ -49,7 +84,6 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
                 List<String> strings() {
                   []
                 }
-              }
             }
 
             apply type: MyPlugin
@@ -78,15 +112,11 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
     def "inputs are fully configured when used in rules"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
-            class MyPlugin {
-              static class Rules extends RuleSource {
+            class MyPlugin extends RuleSource {
                 @Model
                 List<String> strings() {
                   []
                 }
-              }
             }
 
             apply type: MyPlugin
@@ -116,15 +146,11 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
     def "the same input can be referenced more than once, and refers to the same object"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
-            class MyPlugin {
-              static class Rules extends RuleSource {
+            class MyPlugin extends RuleSource {
                 @Model
                 List<String> strings() {
                   []
                 }
-              }
             }
 
             apply type: MyPlugin
@@ -150,10 +176,7 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
         when:
 
         buildScript """
-            import org.gradle.model.*
-
-            class MyPlugin {
-              static class Rules extends RuleSource {
+            class MyPlugin extends RuleSource {
                 @Model
                 String foo() {
                   "foo"
@@ -163,7 +186,6 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
                 List<String> strings() {
                   []
                 }
-              }
             }
 
             subprojects {
@@ -205,15 +227,11 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
     def "only closure literals can be used as rules"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
-            class MyPlugin {
-              static class Rules extends RuleSource {
+            class MyPlugin extends RuleSource {
                 @Model
                 String foo() {
                   "foo"
                 }
-              }
             }
 
             apply type: MyPlugin
@@ -226,7 +244,7 @@ class ModelDslIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         fails "tasks"
-        failure.assertHasLineNumber 17
+        failure.assertHasLineNumber 13
         failure.assertHasFileName("Build file '${buildFile}'")
         failure.assertThatCause(containsString(RulesVisitor.INVALID_RULE_SIGNATURE))
     }
diff --git a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleDetectionIntegrationSpec.groovy b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleDetectionIntegrationSpec.groovy
index 9d47f54..0f117df 100644
--- a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleDetectionIntegrationSpec.groovy
+++ b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleDetectionIntegrationSpec.groovy
@@ -35,8 +35,6 @@ class ModelDslRuleDetectionIntegrationSpec extends AbstractIntegrationSpec {
 
         when:
         buildScript """
-            import org.gradle.model.collection.*
-
             @Managed
             interface Item {
                 String getValue()
@@ -67,7 +65,7 @@ class ModelDslRuleDetectionIntegrationSpec extends AbstractIntegrationSpec {
                 void a(A a) { }
 
                 @Mutate
-                void addTask(CollectionBuilder<Task> tasks, @Path("$normalisedPath") Item item) {
+                void addTask(ModelMap<Task> tasks, @Path("$normalisedPath") Item item) {
                     tasks.create("printValue") {
                         it.doLast {
                             println "value: " + item.value
diff --git a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleInputDetectionIntegrationSpec.groovy b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleInputDetectionIntegrationSpec.groovy
index 179c7ab..74a490a 100644
--- a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleInputDetectionIntegrationSpec.groovy
+++ b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/ModelDslRuleInputDetectionIntegrationSpec.groovy
@@ -18,11 +18,8 @@ package org.gradle.model.dsl.internal.transform
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.EnableModelDsl
-import org.gradle.model.internal.report.unbound.UnboundRule
-import org.gradle.model.internal.report.unbound.UnboundRuleInput
 import spock.lang.Unroll
 
-import static org.gradle.model.report.unbound.UnboundRulesReportMatchers.unbound
 import static org.hamcrest.Matchers.containsString
 
 class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec {
@@ -33,6 +30,79 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
     }
 
     @Unroll
+    def "can reference input - #syntax"() {
+        when:
+        buildScript """
+          @Managed
+          interface Thing {
+            String getValue(); void setValue(String str)
+          }
+
+          model {
+            thing(Thing) {
+                value = "foo"
+            }
+            tasks {
+                create("echo") {
+                    doLast {
+                        println "thing.value: " + $syntax
+                    }
+                }
+            }
+          }
+        """
+
+        then:
+        succeeds "echo"
+        output.contains "thing.value: foo"
+
+        where:
+        syntax << [
+            '$("thing.value")',
+            '$("thing").value',
+            "thing.value",
+            "(thing).value",
+            "(thing).(value)",
+            "thing.\"\${'value'}\"",
+            "thing.'value'",
+            "thing.value.toUpperCase().toLowerCase()",
+            "thing.value[0] + 'oo'",
+            "(true ? thing.value : 'bar')",
+            "new String(thing.value)",
+            "thing.getValue()"
+        ]
+    }
+
+    def "can reference property of subject"() {
+        when:
+        buildScript """
+          @Managed
+          interface Thing {
+            String getValue(); void setValue(String str)
+          }
+
+          model {
+            thing(Thing) {
+              value = "foo"
+              value = value + "-bar"
+            }
+            tasks {
+              create("echo") {
+                doLast {
+                  println "thing.value: " + thing.value
+                }
+              }
+            }
+          }
+        """
+
+        then:
+        succeeds "echo"
+
+        and:
+        output.contains "thing.value: foo-bar"
+    }
+    @Unroll
     def "only literal strings can be given to dollar - #code"() {
         when:
         buildScript """
@@ -66,8 +136,6 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
     def "dollar method is only detected with no explicit receiver - #code"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
               static class Rules extends RuleSource {
                 @Model
@@ -100,12 +168,9 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
     def "input references are found in nested code - #code"() {
         when:
         buildScript """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
-
             class MyPlugin {
                 static class Rules extends RuleSource {
-                    @Mutate void addPrintTask(CollectionBuilder<Task> tasks, List<String> strings) {
+                    @Mutate void addPrintTask(ModelMap<Task> tasks, List<String> strings) {
                         tasks.create("printMessage", PrintTask) {
                             it.message = strings
                         }
@@ -160,8 +225,6 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
     def "input model path must be valid"() {
         when:
         buildScript """
-            import org.gradle.model.*
-
             class MyPlugin {
               static class Rules extends RuleSource {
                 @Model
@@ -182,22 +245,61 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
 
         then:
         fails "tasks"
-        failure.assertHasLineNumber(17)
+        failure.assertHasLineNumber(15)
         failure.assertThatCause(containsString("Invalid model path given as rule input."))
         failure.assertThatCause(containsString("Model path 'foo. bar' is invalid due to invalid name component."))
         failure.assertThatCause(containsString("Model element name ' bar' has illegal first character ' ' (names must start with an ASCII letter or underscore)."))
     }
 
-    def "location and suggestions are provided for unbound rule inputs specified using a name"() {
+    def "location and suggestions are provided for unbound rule subject specified using a name"() {
         given:
         buildScript '''
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
+            class MyPlugin extends RuleSource {
+                @Model
+                String foobar() {
+                    return "value"
+                }
+                @Model
+                String raboof() {
+                    return "value"
+                }
+            }
+
+            apply type: MyPlugin
+
+            model {
+                foonar {
+                }
+                foobah {
+                }
+                fooar {
+                }
+            }
+        '''
+
+        when:
+        fails "tasks"
 
+        then:
+        failure.assertHasCause("""The following model rules are unbound:
+  model.fooar @ build file '${buildFile}' line 20, column 17
+    Mutable:
+      - fooar (java.lang.Object) - suggestions: foobar
+  model.foobah @ build file '${buildFile}' line 18, column 17
+    Mutable:
+      - foobah (java.lang.Object) - suggestions: foobar
+  model.foonar @ build file '${buildFile}' line 16, column 17
+    Mutable:
+      - foonar (java.lang.Object) - suggestions: foobar""")
+    }
+
+    def "location and suggestions are provided for unbound rule inputs specified using a name"() {
+        given:
+        buildScript '''
             class MyPlugin {
                 static class Rules extends RuleSource {
                     @Mutate
-                    void addTasks(CollectionBuilder<Task> tasks) {
+                    void addTasks(ModelMap<Task> tasks) {
                         tasks.create("foobar")
                         tasks.create("raboof")
                     }
@@ -207,10 +309,10 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
             apply type: MyPlugin
 
             model {
-                foo {
+                tasks.raboof {
                     $('tasks.foonar')
                     $('tasks.fooar')
-                    $('tasks.foonar')
+                    $('tasks.foobarr')
                 }
             }
         '''
@@ -219,12 +321,14 @@ class ModelDslRuleInputDetectionIntegrationSpec extends AbstractIntegrationSpec
         fails "tasks"
 
         then:
-        failure.assertThatCause(unbound(
-                UnboundRule.descriptor("model.foo", buildFile, 18, 17)
-                        .mutableInput(UnboundRuleInput.type(Object).path("foo"))
-                        .immutableInput(UnboundRuleInput.type(Object).path("tasks.foonar").suggestions("tasks.foobar").description("@ line 19"))
-                        .immutableInput(UnboundRuleInput.type(Object).path("tasks.fooar").suggestions("tasks.foobar").description("@ line 20"))
-        ))
+        failure.assertHasCause("""The following model rules are unbound:
+  model.tasks.raboof @ build file '${buildFile}' line 15, column 17
+    Mutable:
+      + tasks.raboof (java.lang.Object)
+    Immutable:
+      - tasks.foonar (java.lang.Object) @ line 16 - suggestions: tasks.foobar
+      - tasks.fooar (java.lang.Object) @ line 17 - suggestions: tasks.foobar
+      - tasks.foobarr (java.lang.Object) @ line 18 - suggestions: tasks.foobar""")
     }
 
     def "can not access project or script from rule"() {
diff --git a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/NestedModelDslUsageIntegrationSpec.groovy b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/NestedModelDslUsageIntegrationSpec.groovy
index 704f824..ba9802c 100644
--- a/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/NestedModelDslUsageIntegrationSpec.groovy
+++ b/subprojects/model-groovy/src/integTest/groovy/org/gradle/model/dsl/internal/transform/NestedModelDslUsageIntegrationSpec.groovy
@@ -166,9 +166,9 @@ class NestedModelDslUsageIntegrationSpec extends AbstractIntegrationSpec {
         return """
             class TestPlugin {
                 static class Rules extends org.gradle.model.RuleSource {
-                    @org.gradle.model.Model String foo() { "foo" }
-                    @org.gradle.model.Model List<String> strings() { [] }
-                    @org.gradle.model.Mutate void addTask(org.gradle.model.collection.CollectionBuilder<Task> tasks, List<String> strings) {
+                    @Model String foo() { "foo" }
+                    @Model List<String> strings() { [] }
+                    @Mutate void addTask(ModelMap<Task> tasks, List<String> strings) {
                         tasks.create("printStrings") { it.doLast { println "strings: " + strings } }
                     }
                 }
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java
index 72bb2bf..c5781b8 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java
@@ -65,7 +65,7 @@ public class NonTransformedModelDslBacking extends GroovyObjectSupport {
 
     private void registerConfigurationAction(final Closure<?> action) {
         modelRegistry.configure(ModelActionRole.Mutate,
-                new ActionBackedModelAction<Object>(
+                new NoInputsModelAction<Object>(
                         ModelReference.untyped(modelPath),
                         new SimpleModelRuleDescriptor("model." + modelPath), new ClosureBackedAction<Object>(action)
                 ));
@@ -127,4 +127,4 @@ public class NonTransformedModelDslBacking extends GroovyObjectSupport {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java
index e1d7f4d..5d6f406 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java
@@ -20,11 +20,13 @@ import com.google.common.collect.Lists;
 import groovy.lang.Closure;
 import groovy.lang.DelegatesTo;
 import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.Action;
 import org.gradle.api.Transformer;
 import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.internal.BiAction;
 import org.gradle.model.InvalidModelRuleDeclarationException;
 import org.gradle.model.dsl.internal.inputs.RuleInputAccessBacking;
+import org.gradle.model.dsl.internal.transform.InputReferences;
 import org.gradle.model.dsl.internal.transform.RuleMetadata;
 import org.gradle.model.dsl.internal.transform.RulesBlock;
 import org.gradle.model.dsl.internal.transform.SourceLocation;
@@ -40,16 +42,13 @@ import java.util.List;
 @ThreadSafe
 public class TransformedModelDslBacking {
 
-    private static final Transformer<List<ModelReference<?>>, Closure<?>> INPUT_PATHS_EXTRACTOR = new Transformer<List<ModelReference<?>>, Closure<?>>() {
-        public List<ModelReference<?>> transform(Closure<?> closure) {
+    private static final Transformer<InputReferences, Closure<?>> INPUT_PATHS_EXTRACTOR = new Transformer<InputReferences, Closure<?>>() {
+        public InputReferences transform(Closure<?> closure) {
+            InputReferences inputs = new InputReferences();
             RuleMetadata ruleMetadata = getRuleMetadata(closure);
-            String[] paths = ruleMetadata.inputPaths();
-            List<ModelReference<?>> references = Lists.newArrayListWithCapacity(paths.length);
-            for (int i = 0; i < paths.length; i++) {
-                String description = String.format("@ line %d", ruleMetadata.inputLineNumbers()[i]);
-                references.add(ModelReference.untyped(ModelPath.path(paths[i]), description));
-            }
-            return references;
+            inputs.absolutePaths(ruleMetadata.absoluteInputPaths(), ruleMetadata.absoluteInputLineNumbers());
+            inputs.relativePaths(ruleMetadata.relativeInputPaths(), ruleMetadata.relativeInputLineNumbers());
+            return inputs;
         }
     };
 
@@ -61,7 +60,7 @@ public class TransformedModelDslBacking {
     };
 
     private final ModelRegistry modelRegistry;
-    private final Transformer<? extends List<ModelReference<?>>, ? super Closure<?>> inputPathsExtractor;
+    private final Transformer<? extends InputReferences, ? super Closure<?>> inputPathsExtractor;
     private final Transformer<SourceLocation, ? super Closure<?>> ruleLocationExtractor;
     private final ModelSchemaStore schemaStore;
     private final ModelCreatorFactory modelCreatorFactory;
@@ -70,7 +69,7 @@ public class TransformedModelDslBacking {
         this(modelRegistry, schemaStore, modelCreatorFactory, INPUT_PATHS_EXTRACTOR, RULE_LOCATION_EXTRACTOR);
     }
 
-    TransformedModelDslBacking(ModelRegistry modelRegistry, ModelSchemaStore schemaStore, ModelCreatorFactory modelCreatorFactory, Transformer<? extends List<ModelReference<?>>, ? super Closure<?>> inputPathsExtractor,
+    TransformedModelDslBacking(ModelRegistry modelRegistry, ModelSchemaStore schemaStore, ModelCreatorFactory modelCreatorFactory, Transformer<? extends InputReferences, ? super Closure<?>> inputPathsExtractor,
                                Transformer<SourceLocation, ? super Closure<?>> ruleLocationExtractor) {
         this.modelRegistry = modelRegistry;
         this.schemaStore = schemaStore;
@@ -80,15 +79,13 @@ public class TransformedModelDslBacking {
     }
 
     public void configure(String modelPathString, Closure<?> closure) {
-        List<ModelReference<?>> inputs = inputPathsExtractor.transform(closure);
         SourceLocation sourceLocation = ruleLocationExtractor.transform(closure);
         ModelPath modelPath = ModelPath.path(modelPathString);
-        ModelAction<Object> action = BiActionBackedModelAction.of(ModelReference.of(modelPath), toDescriptor(sourceLocation, modelPath), inputs, new ExecuteClosure<Object>(closure));
-        modelRegistry.configure(ModelActionRole.Mutate, action);
+        ModelRuleDescriptor descriptor = toDescriptor(sourceLocation, modelPath);
+        registerAction(modelPath, Object.class, descriptor, ModelActionRole.Mutate, closure);
     }
 
     public <T> void create(String modelPathString, @DelegatesTo.Target Class<T> type, @DelegatesTo(genericTypeIndex = 0) Closure<?> closure) {
-        List<ModelReference<?>> inputs = inputPathsExtractor.transform(closure);
         SourceLocation sourceLocation = ruleLocationExtractor.transform(closure);
         ModelPath modelPath = ModelPath.path(modelPathString);
         ModelSchema<T> schema = schemaStore.getSchema(ModelType.of(type));
@@ -96,8 +93,36 @@ public class TransformedModelDslBacking {
         if (!schema.getKind().isManaged()) {
             throw new InvalidModelRuleDeclarationException(descriptor, "Cannot create an element of type " + type.getName() + " as it is not a managed type");
         }
-        ModelCreator creator = modelCreatorFactory.creator(descriptor, modelPath, schema, inputs, new ExecuteClosure<T>(closure));
+        ModelCreator creator = modelCreatorFactory.creator(descriptor, modelPath, schema);
         modelRegistry.create(creator);
+        registerAction(modelPath, type, descriptor, ModelActionRole.Initialize, closure);
+    }
+
+    private <T> void registerAction(final ModelPath modelPath, Class<T> viewType, final ModelRuleDescriptor descriptor, final ModelActionRole role, final Closure<?> closure) {
+        final ModelReference<T> reference = ModelReference.of(modelPath, viewType);
+        ModelAction<T> action = DirectNodeNoInputsModelAction.of(reference, descriptor, new Action<MutableModelNode>() {
+            @Override
+            public void execute(MutableModelNode modelNode) {
+                InputReferences inputs = inputPathsExtractor.transform(closure);
+                List<String> absolutePaths = inputs.getAbsolutePaths();
+                List<Integer> absolutePathLineNumbers = inputs.getAbsolutePathLineNumbers();
+                List<String> relativePaths = inputs.getRelativePaths();
+                List<Integer> relativePathLineNumbers = inputs.getRelativePathLineNumbers();
+                List<ModelReference<?>> references = Lists.newArrayListWithCapacity(absolutePaths.size() + inputs.getRelativePaths().size());
+                for (int i = 0; i < absolutePaths.size(); i++) {
+                    String description = String.format("@ line %d", absolutePathLineNumbers.get(i));
+                    references.add(ModelReference.untyped(ModelPath.path(absolutePaths.get(i)), description));
+                }
+                for (int i = 0; i < relativePaths.size(); i++) {
+                    String description = String.format("@ line %d", relativePathLineNumbers.get(i));
+                    references.add(ModelReference.untyped(ModelPath.path(relativePaths.get(i)), description));
+                }
+
+                ModelAction<T> runClosureAction = InputUsingModelAction.of(reference, descriptor, references, new ExecuteClosure<T>(closure));
+                modelRegistry.configure(role, runClosureAction);
+            }
+        });
+        modelRegistry.configure(ModelActionRole.DefineRules, action);
     }
 
     public ModelRuleDescriptor toDescriptor(SourceLocation sourceLocation, ModelPath modelPath) {
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccess.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccess.java
index 45ecaf4..0d4c05d 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccess.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccess.java
@@ -18,6 +18,8 @@ package org.gradle.model.dsl.internal.inputs;
 
 public interface RuleInputAccess {
 
-    public Object input(String modelPath);
+    Object input(String modelPath);
+
+    boolean has(String modelPath);
 
 }
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccessBacking.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccessBacking.java
index 9cbd23d..5c875eb 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccessBacking.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/inputs/RuleInputAccessBacking.java
@@ -49,6 +49,12 @@ public abstract class RuleInputAccessBacking {
     public static RuleInputAccess getAccess() {
         final Map<String, Object> inputs = INPUT.get();
         return new RuleInputAccess() {
+
+            @Override
+            public boolean has(String modelPath) {
+                return inputs.containsKey(modelPath);
+            }
+
             public Object input(String modelPath) {
                 return inputs.get(modelPath);
             }
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/InputReferences.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/InputReferences.java
new file mode 100644
index 0000000..8447e36
--- /dev/null
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/InputReferences.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transform;
+
+import com.google.common.collect.Lists;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class InputReferences {
+    private final List<String> relativePaths = Lists.newArrayList();
+    private final List<Integer> relativePathLineNumbers = Lists.newArrayList();
+    private final List<String> absolutePaths = Lists.newArrayList();
+    private final List<Integer> absolutePathLineNumbers = Lists.newArrayList();
+
+    public List<String> getAbsolutePaths() {
+        return absolutePaths;
+    }
+
+    public List<Integer> getAbsolutePathLineNumbers() {
+        return absolutePathLineNumbers;
+    }
+
+    public List<String> getRelativePaths() {
+        return relativePaths;
+    }
+
+    public List<Integer> getRelativePathLineNumbers() {
+        return relativePathLineNumbers;
+    }
+
+    public void relativePath(String path, int lineNumber) {
+        relativePaths.add(path);
+        relativePathLineNumbers.add(lineNumber);
+    }
+
+    public void absolutePath(String path, int lineNumber) {
+        absolutePaths.add(path);
+        absolutePathLineNumbers.add(lineNumber);
+    }
+
+    public boolean isEmpty() {
+        return relativePaths.isEmpty() && absolutePaths.isEmpty();
+    }
+
+    public void absolutePaths(String[] paths, int[] lineNumbers) {
+        absolutePaths.addAll(Arrays.asList(paths));
+        for (int lineNumber : lineNumbers) {
+            absolutePathLineNumbers.add(lineNumber);
+        }
+    }
+
+    public void relativePaths(String[] paths, int[] lineNumbers) {
+        relativePaths.addAll(Arrays.asList(paths));
+        for (int lineNumber : lineNumbers) {
+            relativePathLineNumbers.add(lineNumber);
+        }
+    }
+}
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleMetadata.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleMetadata.java
index 32e92eb..88a8c19 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleMetadata.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleMetadata.java
@@ -24,9 +24,23 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface RuleMetadata {
-    String[] inputPaths() default {};
-    int[] inputLineNumbers() default {};
+    /**
+     * Definite input references, should be treated as absolute model paths.
+     */
+    String[] absoluteInputPaths() default {};
+
+    int[] absoluteInputLineNumbers() default {};
+
+    /**
+     * Candidate input references, should be resolved relative to the subject of the rule.
+     */
+    String[] relativeInputPaths() default {};
+
+    int[] relativeInputLineNumbers() default {};
+
     String scriptSourceDescription();
+
     int lineNumber();
+
     int columnNumber();
 }
diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleVisitor.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleVisitor.java
index 2a4dd32..00b97c4 100644
--- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleVisitor.java
+++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/RuleVisitor.java
@@ -16,10 +16,7 @@
 
 package org.gradle.model.dsl.internal.transform;
 
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Multimaps;
 import net.jcip.annotations.NotThreadSafe;
 import org.codehaus.groovy.ast.*;
 import org.codehaus.groovy.ast.expr.*;
@@ -31,16 +28,18 @@ import org.codehaus.groovy.syntax.SyntaxException;
 import org.codehaus.groovy.syntax.Token;
 import org.codehaus.groovy.syntax.Types;
 import org.gradle.groovy.scripts.internal.AstUtils;
+import org.gradle.groovy.scripts.internal.ExpressionReplacingVisitorSupport;
 import org.gradle.internal.SystemProperties;
 import org.gradle.model.dsl.internal.inputs.RuleInputAccess;
 import org.gradle.model.dsl.internal.inputs.RuleInputAccessBacking;
 import org.gradle.model.internal.core.ModelPath;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 @NotThreadSafe
-public class RuleVisitor extends CodeVisitorSupport {
+public class RuleVisitor extends ExpressionReplacingVisitorSupport {
 
     public static final String INVALID_ARGUMENT_LIST = "argument list must be exactly 1 literal non empty string";
 
@@ -49,6 +48,7 @@ public class RuleVisitor extends CodeVisitorSupport {
 
     private static final String DOLLAR = "$";
     private static final String INPUT = "input";
+    private static final String HAS = "has";
     private static final ClassNode ANNOTATION_CLASS_NODE = new ClassNode(RuleMetadata.class);
     private static final ClassNode CONTEXTUAL_INPUT_TYPE = new ClassNode(RuleInputAccessBacking.class);
     private static final ClassNode ACCESS_API_TYPE = new ClassNode(RuleInputAccess.class);
@@ -57,7 +57,7 @@ public class RuleVisitor extends CodeVisitorSupport {
     private static final String ACCESS_HOLDER_FIELD = "_" + RuleInputAccess.class.getName().replace(".", "_");
 
     private final SourceUnit sourceUnit;
-    private ImmutableListMultimap.Builder<String, Integer> inputs;
+    private InputReferences inputs;
     private VariableExpression accessVariable;
 
     public RuleVisitor(SourceUnit sourceUnit) {
@@ -76,34 +76,37 @@ public class RuleVisitor extends CodeVisitorSupport {
             metadataAnnotation.addMember("lineNumber", new ConstantExpression(sourceLocation.getLineNumber()));
             metadataAnnotation.addMember("columnNumber", new ConstantExpression(sourceLocation.getColumnNumber()));
 
-            ListMultimap<String, Integer> inputs = closureCode.getNodeMetaData(AST_NODE_METADATA_INPUTS_KEY);
+            InputReferences inputs = closureCode.getNodeMetaData(AST_NODE_METADATA_INPUTS_KEY);
             if (!inputs.isEmpty()) {
-                List<Expression> pathValues = Lists.newArrayListWithCapacity(inputs.size());
-                List<Expression> lineNumberValues = Lists.newArrayListWithCapacity(inputs.size());
-                for (Map.Entry<String, List<Integer>> input : Multimaps.asMap(inputs).entrySet()) {
-                    pathValues.add(new ConstantExpression(input.getKey()));
-                    lineNumberValues.add(new ConstantExpression(input.getValue().get(0)));
-                }
-
-                metadataAnnotation.addMember("inputPaths", new ListExpression(pathValues));
-                metadataAnnotation.addMember("inputLineNumbers", new ListExpression(lineNumberValues));
+                metadataAnnotation.addMember("absoluteInputPaths", new ListExpression(constants(inputs.getAbsolutePaths())));
+                metadataAnnotation.addMember("absoluteInputLineNumbers", new ListExpression(constants(inputs.getAbsolutePathLineNumbers())));
+                metadataAnnotation.addMember("relativeInputPaths", new ListExpression(constants(inputs.getRelativePaths())));
+                metadataAnnotation.addMember("relativeInputLineNumbers", new ListExpression(constants(inputs.getRelativePathLineNumbers())));
             }
 
             node.addAnnotation(metadataAnnotation);
         }
     }
 
+    private static List<Expression> constants(Collection<?> values) {
+        List<Expression> expressions = Lists.newArrayListWithCapacity(values.size());
+        for (Object value : values) {
+            expressions.add(new ConstantExpression(value));
+        }
+        return expressions;
+    }
+
     @Override
     public void visitClosureExpression(ClosureExpression expression) {
         if (inputs == null) {
-            inputs = ImmutableListMultimap.builder();
+            inputs = new InputReferences();
             try {
                 accessVariable = new VariableExpression(ACCESS_HOLDER_FIELD, ACCESS_API_TYPE);
 
                 super.visitClosureExpression(expression);
 
                 BlockStatement code = (BlockStatement) expression.getCode();
-                code.setNodeMetaData(AST_NODE_METADATA_INPUTS_KEY, inputs.build());
+                code.setNodeMetaData(AST_NODE_METADATA_INPUTS_KEY, inputs);
                 accessVariable.setClosureSharedVariable(true);
                 StaticMethodCallExpression getAccessCall = new StaticMethodCallExpression(CONTEXTUAL_INPUT_TYPE, GET_ACCESS, ArgumentListExpression.EMPTY_ARGUMENTS);
                 DeclarationExpression variableDeclaration = new DeclarationExpression(accessVariable, new Token(Types.ASSIGN, "=", -1, -1), getAccessCall);
@@ -129,6 +132,77 @@ public class RuleVisitor extends CodeVisitorSupport {
         }
     }
 
+    @Override
+    public void visitBinaryExpression(BinaryExpression expression) {
+        if (expression.getLeftExpression() instanceof VariableExpression) {
+            expression.setRightExpression(replaceExpr(expression.getRightExpression()));
+        } else {
+            super.visitBinaryExpression(expression);
+        }
+    }
+
+    @Override
+    public void visitPropertyExpression(PropertyExpression expression) {
+        ArrayList<String> names = Lists.newArrayList();
+        boolean propertyNameIsPart = extractPropertyPath(expression, names);
+        if (names.isEmpty() || !names.get(0).equals("thing")) {
+            super.visitPropertyExpression(expression);
+        } else {
+            String modelPath = ModelPath.pathString(names);
+            inputs.relativePath(modelPath, expression.getLineNumber());
+            if (propertyNameIsPart) {
+                replaceVisitedExpressionWith(conditionalInputGet(modelPath, expression));
+            } else {
+                expression.setObjectExpression(conditionalInputGet(modelPath, expression.getObjectExpression()));
+            }
+        }
+    }
+
+    private TernaryExpression conditionalInputGet(String modelPath, Expression originalExpression) {
+        return new TernaryExpression(
+            new BooleanExpression(new MethodCallExpression(accessVariable, HAS, new ArgumentListExpression(new ConstantExpression(modelPath)))),
+            new MethodCallExpression(accessVariable, INPUT, new ArgumentListExpression(new ConstantExpression(modelPath))),
+            originalExpression
+        );
+    }
+
+    private boolean extractPropertyPath(Expression expression, List<String> names) {
+        if (expression instanceof PropertyExpression) {
+            PropertyExpression propertyExpression = (PropertyExpression) expression;
+            if (extractPropertyPath(propertyExpression.getObjectExpression(), names)) {
+                return extractPropertyPath(propertyExpression.getProperty(), names);
+            }
+        } else if (expression instanceof VariableExpression) {
+            names.add(((VariableExpression) expression).getName());
+        } else if (expression instanceof ConstantExpression) {
+            ConstantExpression constantExpression = (ConstantExpression) expression;
+            if (constantExpression.getType().equals(ClassHelper.STRING_TYPE)) {
+                names.add(constantExpression.getText());
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void visitVariableExpression(VariableExpression expression) {
+        if (expression.isThisExpression() || expression.isSuperExpression()) {
+            super.visitVariableExpression(expression);
+        } else {
+            String modelPath = expression.getText();
+            if (modelPath.equals("thing")) {
+                inputs.relativePath(modelPath, expression.getLineNumber());
+                replaceVisitedExpressionWith(conditionalInputGet(modelPath, expression));
+            } else {
+                super.visitVariableExpression(expression);
+            }
+        }
+    }
+
     private void visitInputMethod(MethodCallExpression call) {
         ConstantExpression argExpression = AstUtils.hasSingleConstantStringArg(call);
         if (argExpression == null) { // not a valid signature
@@ -146,7 +220,7 @@ public class RuleVisitor extends CodeVisitorSupport {
                 // TODO find a better way to present this information in the error message
                 // Attempt to mimic Gradle nested exception output
                 String message = "Invalid model path given as rule input." + SystemProperties.getInstance().getLineSeparator()
-                        + "  > " + e.getMessage();
+                    + "  > " + e.getMessage();
                 if (e.getCause() != null) {
                     // if there is a cause, it's an invalid name exception
                     message += SystemProperties.getInstance().getLineSeparator() + "    > " + e.getCause().getMessage();
@@ -155,7 +229,7 @@ public class RuleVisitor extends CodeVisitorSupport {
                 return;
             }
 
-            inputs.put(modelPath, call.getLineNumber());
+            inputs.absolutePath(modelPath, call.getLineNumber());
             call.setObjectExpression(new VariableExpression(accessVariable));
             call.setMethod(new ConstantExpression(INPUT));
         }
@@ -165,4 +239,5 @@ public class RuleVisitor extends CodeVisitorSupport {
         SyntaxException syntaxException = new SyntaxException(message, call.getLineNumber(), call.getColumnNumber());
         sourceUnit.getErrorCollector().addError(syntaxException, sourceUnit);
     }
+
 }
diff --git a/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/NonTransformedModelDslBackingTest.groovy b/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/NonTransformedModelDslBackingTest.groovy
index 9714199..bad4c90 100644
--- a/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/NonTransformedModelDslBackingTest.groovy
+++ b/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/NonTransformedModelDslBackingTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.model.dsl.internal
 
 import org.gradle.model.InvalidModelRuleDeclarationException
 import org.gradle.model.Managed
-import org.gradle.model.collection.ManagedSet
+import org.gradle.model.ModelSet
 import org.gradle.model.internal.core.ModelCreators
 import org.gradle.model.internal.core.ModelPath
 import org.gradle.model.internal.core.ModelReference
@@ -64,7 +64,7 @@ class NonTransformedModelDslBackingTest extends Specification {
 
     @Managed
     interface Foo {
-        ManagedSet<Thing> getBar()
+        ModelSet<Thing> getBar()
     }
 
     interface Unmanaged {}
diff --git a/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/TransformedModelDslBackingTest.groovy b/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/TransformedModelDslBackingTest.groovy
index 3eb4f86..cc2b5ea 100644
--- a/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/TransformedModelDslBackingTest.groovy
+++ b/subprojects/model-groovy/src/test/groovy/org/gradle/model/dsl/internal/TransformedModelDslBackingTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.Transformer
 import org.gradle.model.InvalidModelRuleDeclarationException
 import org.gradle.model.Managed
 import org.gradle.model.dsl.internal.inputs.RuleInputAccessBacking
+import org.gradle.model.dsl.internal.transform.InputReferences
 import org.gradle.model.dsl.internal.transform.SourceLocation
 import org.gradle.model.internal.core.ModelCreators
 import org.gradle.model.internal.core.ModelPath
@@ -47,7 +48,7 @@ class TransformedModelDslBackingTest extends Specification {
     def "can add rules via dsl"() {
         given:
         register("foo", [])
-        referenceExtractor.transform(_) >> []
+        referenceExtractor.transform(_) >> new InputReferences()
         locationExtractor.transform(_) >> Mock(SourceLocation) {
             asDescriptor(_) >> new SimpleModelRuleDescriptor("foo")
         }
@@ -70,7 +71,7 @@ class TransformedModelDslBackingTest extends Specification {
 
     def "can add creator via dsl"() {
         given:
-        referenceExtractor.transform(_) >> []
+        referenceExtractor.transform(_) >> new InputReferences()
         locationExtractor.transform(_) >> Mock(SourceLocation) {
             asDescriptor(_) >> new SimpleModelRuleDescriptor("foo")
         }
@@ -86,7 +87,7 @@ class TransformedModelDslBackingTest extends Specification {
 
     def "can only create top level"() {
         given:
-        referenceExtractor.transform(_) >> []
+        referenceExtractor.transform(_) >> new InputReferences()
         locationExtractor.transform(_) >> Mock(SourceLocation) {
             asDescriptor(_) >> new SimpleModelRuleDescriptor("foo")
         }
@@ -102,9 +103,11 @@ class TransformedModelDslBackingTest extends Specification {
 
     def "can registers extracted references"() {
         given:
+        def inputs = new InputReferences()
+        inputs.absolutePath("value", 123)
         register("foo", [])
         register("value", "123")
-        referenceExtractor.transform(_) >> [ModelReference.of("value", Object)]
+        referenceExtractor.transform(_) >> inputs
         locationExtractor.transform(_) >> Mock(SourceLocation) {
             asDescriptor(_) >> new SimpleModelRuleDescriptor("foo")
         }
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/filesystem/services/FileSystemServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/filesystem/services/FileSystemServices.java
index bc689d4..5aef348 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/filesystem/services/FileSystemServices.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/filesystem/services/FileSystemServices.java
@@ -63,7 +63,7 @@ public class FileSystemServices {
     private Object newInstance(String jdk7Type, Class<?> fallbackType) {
         // Use java 7 APIs, if available
         Class<?> handlerClass = null;
-        if (JavaVersion.current().isJava7()) {
+        if (JavaVersion.current().isJava7Compatible()) {
             try {
                 handlerClass = FileSystemServices.class.getClassLoader().loadClass(jdk7Type);
                 LOGGER.debug("Using JDK 7 file service {}", jdk7Type);
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index 53f0f93..8421611 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -286,6 +286,39 @@ task bigNative(type: ProjectGeneratorTask) {
     subProjectTemplates = ['native-source', 'native-component']
 }
 
+task smallPCHNative(type: ProjectGeneratorTask) {
+    projects = 1
+    sourceFiles = 20
+    nativeProject = true
+    templateArgs = [
+            moduleCount: 1,
+            functionCount: 1
+    ]
+    subProjectTemplates = ['native-pch-source', 'native-pch-component']
+}
+
+task mediumPCHNative(type: ProjectGeneratorTask) {
+    projects = 1
+    sourceFiles = 200
+    nativeProject = true
+    templateArgs = [
+            moduleCount: 1,
+            functionCount: 1
+    ]
+    subProjectTemplates = ['native-pch-source', 'native-pch-component']
+}
+
+task bigPCHNative(type: ProjectGeneratorTask) {
+    projects = 1
+    sourceFiles = 1000
+    nativeProject = true
+    templateArgs = [
+            moduleCount: 1,
+            functionCount: 1
+    ]
+    subProjectTemplates = ['native-pch-source', 'native-pch-component']
+}
+
 task multiNative(type: ProjectGeneratorTask) {
     projects = 10
     sourceFiles = 20
@@ -351,7 +384,7 @@ task all(dependsOn: generators)
 task prepareSamples(dependsOn: [bigEmpty, small, multi, lotDependencies, withJUnit, withTestNG, withVerboseTestNG, withVerboseJUnit, manyProjects,
                                 smallOldJava, mediumOldJava, bigOldJava, smallNewJava, mediumNewJava, bigNewJava,
                                 smallVariantsNewModel, mediumVariantsNewModel, bigVariantsNewModel, smallVariantsOldModel, mediumVariantsOldModel, bigVariantsOldModel,
-                                variantsNewModelMultiproject, variantsOldModelMultiproject, smallNative, mediumNative, bigNative, multiNative,
+                                variantsNewModelMultiproject, variantsOldModelMultiproject, smallNative, mediumNative, bigNative, smallPCHNative, mediumPCHNative, bigPCHNative, multiNative,
                                 smallScenarioNative, mediumScenarioNative, bigScenarioNative, manyProjectsNative,
                                 bigOldJavaMoreSource, lotProjectDependencies])
 
diff --git a/subprojects/performance/src/generator.groovy b/subprojects/performance/src/generator.groovy
index a827158..8413ed6 100644
--- a/subprojects/performance/src/generator.groovy
+++ b/subprojects/performance/src/generator.groovy
@@ -229,6 +229,10 @@ class ProjectGeneratorTask extends DefaultTask {
                 }
             }
             if (nativeProject) {
+                args.moduleCount.times { m ->
+                    Map classArgs = args + [componentName: "lib${m + 1}"]
+                    generate("src/${classArgs.componentName}/headers/pch.h", 'pch.h', classArgs)
+                }
                 testProject.sourceFiles.times { s ->
                     args.moduleCount.times { m ->
                         Map classArgs = args + [componentName: "lib${m + 1}", functionName: "lib${s + 1}"]
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativePreCompiledHeaderPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativePreCompiledHeaderPerformanceTest.groovy
new file mode 100644
index 0000000..514b216
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativePreCompiledHeaderPerformanceTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Unroll
+
+
+class NativePreCompiledHeaderPerformanceTest extends AbstractCrossBuildPerformanceTest {
+    @Unroll
+    def "#size pch performance test" () {
+        when:
+        runner.testId = "native pch build ${size}"
+        runner.testGroup = 'pre-compiled header builds'
+        runner.buildSpec {
+            projectName("${size}PCHNative").displayName("Using PCH").invocation {
+                args("-PusePCH")
+                tasksToRun("clean", "assemble")
+            }
+        }
+        runner.baseline {
+            projectName("${size}PCHNative").displayName("No PCH").invocation {
+                tasksToRun("clean", "assemble")
+            }
+        }
+
+        then:
+        runner.run()
+
+        where:
+        size << [ "small", "medium", "big" ]
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativeScenarioPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativeScenarioPerformanceTest.groovy
index a754c25..09f99a9 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativeScenarioPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/NativeScenarioPerformanceTest.groovy
@@ -15,7 +15,6 @@
  */
 
 package org.gradle.performance
-
 import org.gradle.performance.fixture.BuildExperimentSpec
 import spock.lang.Unroll
 
diff --git a/subprojects/performance/src/templates/native-pch-component/build.gradle b/subprojects/performance/src/templates/native-pch-component/build.gradle
new file mode 100644
index 0000000..f98a8c0
--- /dev/null
+++ b/subprojects/performance/src/templates/native-pch-component/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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: 'c'
+
+model {
+    components {
+        <% moduleCount.times { %>
+        lib${it+1}(NativeLibrarySpec) {
+            sources {
+                c {
+                    if (project.hasProperty("usePCH")) {
+                        preCompiledHeader "pch.h"
+                    }
+                }
+                binaries.all {
+                    if (toolChain.name == "visualCpp") {
+                        cCompiler.args("/showIncludes")
+                    } else {
+                        cCompiler.args("-H")
+                    }
+                }
+            }
+        }
+        <% } %>
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/native-pch-source/lib.c b/subprojects/performance/src/templates/native-pch-source/lib.c
new file mode 100644
index 0000000..a8b13ed
--- /dev/null
+++ b/subprojects/performance/src/templates/native-pch-source/lib.c
@@ -0,0 +1,8 @@
+#include "pch.h"
+
+<% functionCount.times { %>
+int ${functionName}_${it+1} () {
+  printf("Hello world!");
+  return 0;
+}
+<% } %>
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/native-pch-source/pch.h b/subprojects/performance/src/templates/native-pch-source/pch.h
new file mode 100644
index 0000000..dab4c2d
--- /dev/null
+++ b/subprojects/performance/src/templates/native-pch-source/pch.h
@@ -0,0 +1,10 @@
+#ifndef PCH_H
+#define PCH_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+#endif
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/project-with-source/build.gradle b/subprojects/performance/src/templates/project-with-source/build.gradle
index f842723..6e22cfc 100644
--- a/subprojects/performance/src/templates/project-with-source/build.gradle
+++ b/subprojects/performance/src/templates/project-with-source/build.gradle
@@ -62,6 +62,10 @@ tasks.withType(ScalaCompile) {
 <% if (binding.hasVariable("resolveDependenciesTask")) { %>
 task resolveDependencies {
     dependsOn configurations.testRuntime
+    // Need this to ensure that configuration is actually resolved
+    doLast {
+        configurations.testRuntime.files.size()
+    }
 }
 <% } %>
 
diff --git a/subprojects/performance/src/templates/variants-new-model/build.gradle b/subprojects/performance/src/templates/variants-new-model/build.gradle
index ef44546..560a155 100644
--- a/subprojects/performance/src/templates/variants-new-model/build.gradle
+++ b/subprojects/performance/src/templates/variants-new-model/build.gradle
@@ -3,9 +3,9 @@ import org.gradle.model.collection.*
 
 @Managed
 interface Domain {
-    ManagedSet<Flavour> getFlavours()
-    ManagedSet<Type> getTypes()
-    ManagedSet<Variant> getVariants()
+    ModelSet<Flavour> getFlavours()
+    ModelSet<Type> getTypes()
+    ModelSet<Variant> getVariants()
 }
 
 @Managed
@@ -191,4 +191,4 @@ class MyTaskClass extends DefaultTask {
     @Input
     boolean flag
 
-}
\ No newline at end of file
+}
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
index b0a0f4d..17df46b 100644
--- a/subprojects/performance/src/test/groovy/org/gradle/performance/results/ReportGeneratorTest.groovy
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/results/ReportGeneratorTest.groovy
@@ -18,8 +18,10 @@ package org.gradle.performance.results
 
 import org.gradle.performance.ResultSpecification
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
+ at LeaksFileHandles
 class ReportGeneratorTest extends ResultSpecification {
     @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final ReportGenerator generator = new ReportGenerator()
diff --git a/subprojects/platform-base/platform-base.gradle b/subprojects/platform-base/platform-base.gradle
index 0aa76f2..b309db5 100644
--- a/subprojects/platform-base/platform-base.gradle
+++ b/subprojects/platform-base/platform-base.gradle
@@ -1,10 +1,12 @@
 dependencies {
     compile libraries.groovy
     compile project(":core")
+    testFixturesCompile project(path: ":modelCore", configuration: "testFixturesUsageRuntime")
 }
 
 useClassycle()
 strictCompile()
 useTestFixtures()
 useTestFixtures(sourceSet: 'testFixtures')
+useTestFixtures(project: ':diagnostics', sourceSet: 'testFixtures')
 
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/AssembleTaskIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/AssembleTaskIntegrationTest.groovy
index af2487f..cb3ea8f 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/AssembleTaskIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/AssembleTaskIntegrationTest.groovy
@@ -91,8 +91,6 @@ class AssembleTaskIntegrationTest extends AbstractIntegrationSpec {
 
     def withSampleBinary() {
         buildFile << """
-            import org.gradle.model.*
-            import org.gradle.model.collection.*
             import org.gradle.platform.base.internal.BinaryBuildAbility
 
             interface SampleBinary extends BinarySpec {
@@ -112,7 +110,7 @@ class AssembleTaskIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     @Mutate
-                    void createSampleBinary(CollectionBuilder<SampleBinary> binarySpecs) {
+                    void createSampleBinary(ModelMap<SampleBinary> binarySpecs) {
                         ${generateBinaries(binaries)}
                     }
                 }
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentModelIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentModelIntegrationTest.groovy
new file mode 100644
index 0000000..3550671
--- /dev/null
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentModelIntegrationTest.groovy
@@ -0,0 +1,838 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
+import org.gradle.util.TextUtil
+import spock.lang.Unroll
+
+class ComponentModelIntegrationTest extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        EnableModelDsl.enable(executer)
+
+        buildScript """
+            interface CustomComponent extends ComponentSpec {}
+            class DefaultCustomComponent extends BaseComponentSpec implements CustomComponent {}
+
+            class ComponentTypeRules extends RuleSource {
+                @ComponentType
+                void registerCustomComponentType(ComponentTypeBuilder<CustomComponent> builder) {
+                    builder.defaultImplementation(DefaultCustomComponent)
+                }
+            }
+
+            apply type: ComponentTypeRules
+
+            model {
+                components {
+                    main(CustomComponent)
+                }
+            }
+        """
+    }
+
+    void withCustomLanguage() {
+        buildFile << """
+            interface CustomLanguageSourceSet extends LanguageSourceSet {
+                String getData();
+            }
+            class DefaultCustomLanguageSourceSet extends BaseLanguageSourceSet implements CustomLanguageSourceSet {
+                final String data = "foo"
+
+                public boolean getMayHaveSources() {
+                    true
+                }
+            }
+
+            class LanguageTypeRules extends RuleSource {
+                @LanguageType
+                void registerCustomLanguage(LanguageTypeBuilder<CustomLanguageSourceSet> builder) {
+                    builder.defaultImplementation(DefaultCustomLanguageSourceSet)
+                }
+            }
+
+            apply type: LanguageTypeRules
+        """
+    }
+
+    void withMainSourceSet() {
+        withCustomLanguage()
+        buildFile << """
+            model {
+                components {
+                    main {
+                        sources {
+                            main(CustomLanguageSourceSet)
+                        }
+                    }
+                }
+            }
+        """
+    }
+
+    void withBinaries() {
+        buildFile << """
+            interface CustomBinary extends BinarySpec {
+                String getData();
+            }
+            class DefaultCustomBinary extends BaseBinarySpec implements CustomBinary {
+                final String data = "bar"
+            }
+
+            class BinaryRules extends RuleSource {
+                @BinaryType
+                void registerCustomBinary(BinaryTypeBuilder<CustomBinary> builder) {
+                    builder.defaultImplementation(DefaultCustomBinary)
+                }
+
+                @ComponentBinaries
+                void addBinaries(ModelMap<CustomBinary> binaries, CustomComponent component) {
+                    binaries.create("main", CustomBinary)
+                    binaries.create("test", CustomBinary)
+                }
+            }
+
+            apply type: BinaryRules
+
+            model {
+                components {
+                    test(CustomComponent)
+                }
+            }
+        """
+    }
+
+    void withLanguageTransforms() {
+        withMainSourceSet()
+        buildFile << """
+            import org.gradle.language.base.internal.registry.*
+            import org.gradle.language.base.internal.*
+            import org.gradle.language.base.*
+            import org.gradle.internal.reflect.*
+
+
+            class CustomLanguageTransformation implements LanguageTransform {
+                Class getSourceSetType() {
+                    CustomLanguageSourceSet
+                }
+
+                Class getOutputType() {
+                    CustomTransformationFileType
+                }
+
+                Map<String, Class<?>> getBinaryTools() {
+                    throw new UnsupportedOperationException()
+                }
+
+                SourceTransformTaskConfig getTransformTask() {
+                    new SourceTransformTaskConfig() {
+                        String getTaskPrefix() {
+                            "custom"
+                        }
+
+                        Class<? extends DefaultTask> getTaskType() {
+                            DefaultTask
+                        }
+
+                        void configureTask(Task task, BinarySpec binary, LanguageSourceSet sourceSet) {
+                        }
+                    }
+                }
+
+                boolean applyToBinary(BinarySpec binary) {
+                    true
+                }
+            }
+
+            class CustomTransformationFileType implements TransformationFileType {
+            }
+
+
+            class LanguageRules extends RuleSource {
+                @Mutate
+                void registerLanguageTransformation(LanguageTransformContainer transforms) {
+                    transforms.add(new CustomLanguageTransformation())
+                }
+            }
+
+            apply type: LanguageRules
+        """
+    }
+
+    def "component container is visible to rules as various types"() {
+        buildFile << """
+class Rules extends RuleSource {
+    @Defaults
+    void verifyAsContainer(ComponentSpecContainer c) {
+        assert c.toString() == "ComponentSpecContainer 'components'"
+        assert c.withType(CustomComponent).toString() == "ModelMap<CustomComponent> 'components'"
+        assert !(c.withType(CustomComponent) instanceof ComponentSpecContainer)
+    }
+
+    @Defaults
+    void verifyAsModelMap(ModelMap<ComponentSpec> c) {
+        assert c.toString() == "ModelMap<ComponentSpec> 'components'"
+        assert c.withType(CustomComponent).toString() == "ModelMap<CustomComponent> 'components'"
+        assert !(c instanceof ComponentSpecContainer)
+    }
+
+    @Defaults
+    void verifyAsSpecializedModelMap(ModelMap<CustomComponent> c) {
+        assert c.toString() == "ModelMap<CustomComponent> 'components'"
+        assert !(c instanceof ComponentSpecContainer)
+    }
+
+    @Defaults
+    void verifyAsCollectionBuilder(CollectionBuilder<ComponentSpec> c) {
+//        assert c.toString() == "CollectionBuilder<ComponentSpec> 'components'"
+        assert !(c instanceof ComponentSpecContainer)
+    }
+
+    @Defaults
+    void verifyAsSpecializedCollectionBuilder(CollectionBuilder<CustomComponent> c) {
+//        assert c.toString() == "CollectionBuilder<CustomComponent> 'components'"
+        assert !(c instanceof ComponentSpecContainer)
+    }
+}
+
+apply plugin: Rules
+
+model {
+    components {
+        assert it.toString() == "ComponentSpecContainer 'components'"
+        assert it instanceof ComponentSpecContainer
+    }
+}
+"""
+
+        expect:
+        succeeds 'tasks'
+    }
+
+    def "component sources and binaries containers are visible in model report"() {
+        when:
+        succeeds "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    components
+        main
+            binaries
+            sources"""))
+    }
+
+    def "can reference sources container for a component in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printSourceNames") {
+                        def sources = $("components.main.sources")
+                        doLast {
+                            println "names: ${sources.values()*.name}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printSourceNames"
+
+        then:
+        output.contains "names: [main]"
+    }
+
+    def "component sources container elements are visible in model report"() {
+        given:
+        withMainSourceSet()
+        buildFile << """
+            model {
+                components {
+                    main {
+                        sources {
+                            test(CustomLanguageSourceSet)
+                        }
+                    }
+                    test(CustomComponent) {
+                        sources {
+                            test(CustomLanguageSourceSet)
+                        }
+                    }
+                    foo(CustomComponent) {
+                        sources {
+                            bar(CustomLanguageSourceSet)
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    components
+        foo
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'foo:bar'
+        main
+            binaries
+            sources
+                main = DefaultCustomLanguageSourceSet 'main:main'
+                test = DefaultCustomLanguageSourceSet 'main:test'
+        test
+            binaries
+            sources
+                test = DefaultCustomLanguageSourceSet 'test:test'"""))
+    }
+
+    def "can reference sources container elements in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printSourceDisplayName") {
+                        def sources = $("components.main.sources.main")
+                        doLast {
+                            println "sources display name: ${sources.displayName}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printSourceDisplayName"
+
+        then:
+        output.contains "sources display name: DefaultCustomLanguageSourceSet 'main:main'"
+    }
+
+    def "can reference sources container elements using specialized type in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            class TaskRules extends RuleSource {
+                @Mutate
+                void addPrintSourceDisplayNameTask(ModelMap<Task> tasks, @Path("components.main.sources.main") CustomLanguageSourceSet sourceSet) {
+                    tasks.create("printSourceData") {
+                        doLast {
+                            println "sources data: ${sourceSet.data}"
+                        }
+                    }
+                }
+            }
+
+            apply type: TaskRules
+        '''
+
+        when:
+        succeeds "printSourceData"
+
+        then:
+        output.contains "sources data: foo"
+    }
+
+    def "cannot remove source sets"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            class SourceSetRemovalRules extends RuleSource {
+                @Mutate
+                void clearSourceSets(@Path("components.main.sources") NamedDomainObjectCollection<LanguageSourceSet> sourceSets) {
+                    sourceSets.clear()
+                }
+
+                @Mutate
+                void closeMainComponentSourceSetsForTasks(ModelMap<Task> tasks, @Path("components.main.sources") NamedDomainObjectCollection<LanguageSourceSet> sourceSets) {
+                }
+            }
+
+            apply type: SourceSetRemovalRules
+        '''
+
+        when:
+        fails()
+
+        then:
+        failureHasCause("This collection does not support element removal.")
+    }
+
+    def "plugin can create component"() {
+        when:
+        buildFile << """
+        class SomeComponentPlugin extends RuleSource {
+            @Mutate
+            void createComponent(ComponentSpecContainer specs) {
+                specs.create("someCustomComponent", CustomComponent)
+            }
+        }
+        apply plugin: SomeComponentPlugin
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""    components
+        main
+            binaries
+            sources
+        someCustomComponent
+            binaries
+            sources
+"""))
+
+    }
+
+    def "plugin can configure component with given name"() {
+        given:
+        withMainSourceSet()
+        when:
+        buildFile << """
+        class SomeComponentPlugin extends RuleSource {
+            @Mutate
+            void addSourceSet(ComponentSpecContainer specs) {
+                specs.named("main") {
+                    sources {
+                        bar(CustomLanguageSourceSet)
+                    }
+                }
+
+            }
+        }
+        apply plugin: SomeComponentPlugin
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""    components
+        main
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'main:bar'
+                main = DefaultCustomLanguageSourceSet 'main:main'"""))
+    }
+
+    def "plugin can apply component beforeEach / afterEach"() {
+        when:
+        buildFile << """
+        class SomeComponentPlugin extends RuleSource {
+            @Mutate
+            void applyCustomizations(ComponentSpecContainer specs) {
+                specs.create("newComponent", CustomComponent) {
+                    println "creating \$it"
+                }
+                specs.afterEach {
+                    println "afterEach \$it"
+                }
+                specs.beforeEach {
+                    println "beforeEach \$it"
+                }
+            }
+        }
+        apply plugin: SomeComponentPlugin
+        """
+        then:
+        succeeds "tasks"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""beforeEach DefaultCustomComponent 'newComponent'
+creating DefaultCustomComponent 'newComponent'
+afterEach DefaultCustomComponent 'newComponent'"""))
+
+    }
+
+    def "plugin can configure component with given type "() {
+        given:
+        withMainSourceSet()
+        when:
+        buildFile << """
+        class SomeComponentPlugin extends RuleSource {
+            @Mutate
+            void applyCustomizations(ComponentSpecContainer specs) {
+                specs.withType(CustomComponent) {
+                    sources {
+                        bar(CustomLanguageSourceSet)
+                    }
+                }
+            }
+        }
+        apply plugin: SomeComponentPlugin
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""    components
+        main
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'main:bar'
+                main = DefaultCustomLanguageSourceSet 'main:main'"""))
+
+    }
+
+    def "buildscript can create component"() {
+        when:
+        buildFile << """
+        model {
+            components {
+                someCustomComponent(CustomComponent)
+            }
+        }
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+        someCustomComponent
+            binaries
+            sources"""))
+
+    }
+
+    def "buildscript can configure component with given name"() {
+        given:
+        withMainSourceSet()
+        when:
+        buildFile << """
+        model {
+            components {
+                test(CustomComponent)
+
+                named("main") {
+                    sources {
+                        bar(CustomLanguageSourceSet)
+                    }
+                }
+            }
+        }
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""    components
+        main
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'main:bar'
+                main = DefaultCustomLanguageSourceSet 'main:main'
+        test
+            binaries
+            sources
+"""))
+
+    }
+
+    def "buildscript can apply component beforeEach / afterEach"() {
+        given:
+        withMainSourceSet()
+        when:
+        buildFile << """
+        model {
+            components {
+               newComponent(CustomComponent){
+                    println "creating \$it"
+               }
+               beforeEach {
+                    println "beforeEach \$it"
+               }
+
+               afterEach {
+                    println "afterEach \$it"
+               }
+            }
+        }
+        """
+        then:
+        succeeds "tasks"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""beforeEach DefaultCustomComponent 'newComponent'
+creating DefaultCustomComponent 'newComponent'
+afterEach DefaultCustomComponent 'newComponent'"""))
+
+    }
+
+    def "buildscript can configure component with given type "() {
+        given:
+        withMainSourceSet()
+        when:
+        buildFile << """
+        model {
+            components {
+                withType(CustomComponent.class) {
+                    sources {
+                        bar(CustomLanguageSourceSet)
+                    }
+                }
+            }
+        }
+        """
+        then:
+        succeeds "model"
+
+        and:
+        output.contains(TextUtil.toPlatformLineSeparators("""    components
+        main
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'main:bar'
+                main = DefaultCustomLanguageSourceSet 'main:main'"""))
+
+    }
+
+    def "reasonable error message when creating component with default implementation"() {
+        when:
+        buildFile << """
+        model {
+            components {
+                another(DefaultCustomComponent)
+            }
+        }
+
+        """
+        then:
+        fails "model"
+
+        and:
+        failureHasCause("Cannot create a DefaultCustomComponent because this type is not known to this collection. Known types are: CustomComponent")
+    }
+
+    def "reasonable error message when creating component with no implementation"() {
+        when:
+        buildFile << """
+        interface AnotherCustomComponent extends ComponentSpec {}
+
+        model {
+            components {
+                another(AnotherCustomComponent)
+            }
+        }
+
+        """
+        then:
+        fails "model"
+
+        and:
+        failureHasCause("Cannot create a AnotherCustomComponent because this type is not known to this collection. Known types are: CustomComponent")
+    }
+
+    def "componentSpecContainer is groovy decorated when used in rules"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            class ComponentSpecContainerRules extends RuleSource {
+                @Mutate
+                void addComponents(ComponentSpecContainer componentSpecs) {
+                    componentSpecs.anotherCustom(CustomComponent) {
+                    }
+                }
+
+                @Mutate
+                void addComponentTasks(TaskContainer tasks, ComponentSpecContainer componentSpecs) {
+                    tasks.create("printMainComponent") {
+                        doLast{
+                            //reference by name
+                            println "Main component: " + componentSpecs.main.name
+                        }
+
+                    }
+                }
+            }
+
+            apply type: ComponentSpecContainerRules
+        '''
+
+        when:
+        succeeds "printMainComponent"
+        then:
+        output.contains("Main component: main")
+    }
+
+    @Unroll
+    def "#projectionType is closed when used as input"() {
+        given:
+        withMainSourceSet()
+        buildFile << """
+            class ComponentSpecContainerRules extends RuleSource {
+
+                @Mutate
+                void addComponentTasks(TaskContainer tasks, $projectionType componentSpecs) {
+                    componentSpecs.all {
+                        // some stuff here
+                    }
+                }
+            }
+
+            apply type: ComponentSpecContainerRules
+        """
+
+        when:
+        fails "tasks"
+        then:
+        failureHasCause "Attempt to mutate closed view of model of type '$fullQualified' given to rule 'ComponentSpecContainerRules#addComponentTasks(org.gradle.api.tasks.TaskContainer, $fullQualified)'"
+
+        where:
+        projectionType                     | fullQualified
+        "CollectionBuilder<ComponentSpec>" | "org.gradle.model.collection.CollectionBuilder<org.gradle.platform.base.ComponentSpec>"
+        "ModelMap<ComponentSpec>"          | "org.gradle.model.ModelMap<org.gradle.platform.base.ComponentSpec>"
+        "ComponentSpecContainer"           | "org.gradle.platform.base.ComponentSpecContainer"
+    }
+
+    def "component binaries container elements and their tasks containers are visible in model report"() {
+        given:
+        withBinaries()
+
+        when:
+        succeeds "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    components
+        main
+            binaries
+                main
+                    tasks = []
+                test
+                    tasks = []
+            sources
+        test
+            binaries
+                main
+                    tasks = []
+                test
+                    tasks = []
+            sources"""))
+    }
+
+    def "can reference binaries container for a component in a rule"() {
+        given:
+        withBinaries()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printBinaryNames") {
+                        def binaries = $("components.main.binaries")
+                        doLast {
+                            println "names: ${binaries.keySet().toList()}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printBinaryNames"
+
+        then:
+        output.contains "names: [main, test]"
+    }
+
+    def "can reference binaries container elements using specialized type in a rule"() {
+        given:
+        withBinaries()
+        buildFile << '''
+            class TaskRules extends RuleSource {
+                @Mutate
+                void addPrintSourceDisplayNameTask(ModelMap<Task> tasks, @Path("components.main.binaries.main") CustomBinary binary) {
+                    tasks.create("printBinaryData") {
+                        doLast {
+                            println "binary data: ${binary.data}"
+                        }
+                    }
+                }
+            }
+
+            apply type: TaskRules
+        '''
+
+        when:
+        succeeds "printBinaryData"
+
+        then:
+        output.contains "binary data: bar"
+    }
+
+    def "can reference task container of a binary in a rule"() {
+        given:
+        withBinaries()
+        withLanguageTransforms()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printBinaryTaskNames") {
+                        def tasks = $("components.main.binaries.main.tasks")
+                        doLast {
+                            println "names: ${tasks*.name}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printBinaryTaskNames"
+
+        then:
+        output.contains "names: [customMainMainMain]"
+    }
+
+    def "can view components container as a model map and as a collection builder"() {
+        given:
+        buildFile << '''
+            class ComponentsRules extends RuleSource {
+                @Mutate
+                void addViaCollectionBuilder(@Path("components") CollectionBuilder<ComponentSpec> components) {
+                    components.create("viaCollectionBuilder", CustomComponent)
+                }
+
+                @Mutate
+                void addViaModelMap(@Path("components") ModelMap<ComponentSpec> components) {
+                    components.create("viaModelMap", CustomComponent)
+                }
+
+                @Mutate
+                void addPrintComponentNamesTask(ModelMap<Task> tasks, ComponentSpecContainer components) {
+                    tasks.create("printComponentNames") {
+                        doLast {
+                            println "component names: ${components.values()*.name}"
+                        }
+                    }
+                }
+            }
+
+            apply type: ComponentsRules
+        '''
+
+        when:
+        succeeds "printComponentNames"
+
+        then:
+        output.contains "component names: [main, viaCollectionBuilder, viaModelMap]"
+    }
+}
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentTypeSampleIntegTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentTypeSampleIntegTest.groovy
index 4d2e9c8..fc52f1c 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentTypeSampleIntegTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/ComponentTypeSampleIntegTest.groovy
@@ -16,28 +16,39 @@
 
 package org.gradle.language.base
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
 class ComponentTypeSampleIntegTest extends AbstractIntegrationSpec {
     @Rule Sample componentTypeSample = new Sample(temporaryFolder, "customModel/componentType")
 
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
     def "can create custom component with binaries"() {
         given:
         sample componentTypeSample
-        componentTypeSample.dir.file("build.gradle") << """
+        componentTypeSample.dir.file("build.gradle") << '''
 
-task checkModel << {
-    assert project.componentSpecs.size() == 2
-    def titleAImage = project.componentSpecs.imageA
-    assert titleAImage instanceof ImageComponent
-    assert titleAImage.projectPath == project.path
-    assert titleAImage.displayName == "DefaultImageComponent 'imageA'"
-    assert titleAImage.title == 'TitleA'
-    assert titleAImage.binaries.collect{it.name}.sort() == ['TitleA14pxBinary', 'TitleA28pxBinary', 'TitleA40pxBinary']
+model {
+    tasks {
+        create("checkModel") {
+            def components = $("components")
+            doLast {
+                assert components.size() == 2
+                def titleAImage = components.imageA
+                assert titleAImage instanceof ImageComponent
+                assert titleAImage.projectPath == project.path
+                assert titleAImage.displayName == "DefaultImageComponent 'imageA'"
+                assert titleAImage.title == 'TitleA\'
+                assert titleAImage.binaries.values()*.name.sort() == ['TitleA14pxBinary', 'TitleA28pxBinary', 'TitleA40pxBinary']
+            }
+        }
+    }
 }
-
-"""
+'''
         expect:
         succeeds "checkModel"
     }
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryIntegrationTest.groovy
index f62835c..ee435dc 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryIntegrationTest.groovy
@@ -21,9 +21,6 @@ import org.gradle.util.TextUtil
 class CustomBinaryIntegrationTest extends AbstractIntegrationSpec {
     def "setup"() {
         buildFile << """
-import org.gradle.model.*
-import org.gradle.model.collection.*
-
 interface SampleBinary extends BinarySpec {
     String getVersion()
     void setVersion(String version)
@@ -129,7 +126,7 @@ model {
 
             static class Rules extends RuleSource {
                 @Mutate
-                void createSampleBinaries(CollectionBuilder<SampleBinary> binaries) {
+                void createSampleBinaries(ModelMap<SampleBinary> binaries) {
                     binaries.create("sampleBinary")
                 }
 
@@ -165,7 +162,7 @@ model {
                 }
 
                 @Mutate
-                void createSampleBinaryInstances(CollectionBuilder<SampleBinary> binaries) {
+                void createSampleBinaryInstances(ModelMap<SampleBinary> binaries) {
                     binaries.create("sampleBinary")
                 }
 
@@ -175,7 +172,7 @@ model {
                 }
 
                 @Mutate
-                void createAnotherSampleBinaryInstances(CollectionBuilder<AnotherSampleBinary> anotherBinaries) {
+                void createAnotherSampleBinaryInstances(ModelMap<AnotherSampleBinary> anotherBinaries) {
                     anotherBinaries.create("anotherSampleBinary")
                 }
             }
@@ -288,7 +285,7 @@ BUILD SUCCESSFUL"""))
                 }
 
                 @Mutate
-                void createSampleBinary(CollectionBuilder<SampleBinary> binarySpecs) {
+                void createSampleBinary(ModelMap<SampleBinary> binarySpecs) {
                     println "creating binary"
                     binarySpecs.create("sampleBinary")
                 }
@@ -299,4 +296,4 @@ BUILD SUCCESSFUL"""))
         """
     }
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryTasksIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryTasksIntegrationTest.groovy
index a25f27f..72cd7a2 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryTasksIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomBinaryTasksIntegrationTest.groovy
@@ -23,9 +23,6 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
 
     def "setup"() {
         buildFile << """
-        import org.gradle.model.*
-        import org.gradle.model.collection.*
-
         interface SampleBinary extends BinarySpec {}
         class DefaultSampleBinary extends BaseBinarySpec implements SampleBinary {}
         interface SampleLibrary extends ComponentSpec {}
@@ -46,12 +43,12 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void createSampleComponentComponents(CollectionBuilder<SampleLibrary> componentSpecs) {
+                void createSampleComponentComponents(ModelMap<SampleLibrary> componentSpecs) {
                     componentSpecs.create("sampleLib")
                 }
 
                 @ComponentBinaries
-                void createBinariesForSampleLibrary(CollectionBuilder<SampleBinary> binaries, SampleLibrary library) {
+                void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleLibrary library) {
                     binaries.create("\${library.name}BinaryOne")
                     binaries.create("\${library.name}BinaryTwo")
                 }
@@ -72,7 +69,7 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
 
             static class Rules extends RuleSource {
                 @BinaryTasks
-                void createSampleComponentComponents(CollectionBuilder<Task> tasks, SampleBinary binary) {
+                void createSampleComponentComponents(ModelMap<Task> tasks, SampleBinary binary) {
                     tasks.create("\${binary.name}Task")
                 }
             }
@@ -91,7 +88,8 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
         "sampleLibBinaryOne" | "binary lifecycle task"
     }
 
-    def "can reference rule-added tasks in model"() {
+    @Unroll
+    def "can use CollectionBuilder as the first parameter of a BinaryTasks annotated rule"() {
         given:
         buildFile << """
         class BinaryTasksPlugin implements Plugin<Project> {
@@ -100,6 +98,26 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
             static class Rules extends RuleSource {
                 @BinaryTasks
                 void createSampleComponentComponents(CollectionBuilder<Task> tasks, SampleBinary binary) {
+                    tasks.create("usingCollectionBuilder")
+                }
+            }
+        }
+        apply plugin: BinaryTasksPlugin
+"""
+
+        expect:
+        succeeds "usingCollectionBuilder"
+    }
+
+    def "can reference rule-added tasks in model"() {
+        given:
+        buildFile << """
+        class BinaryTasksPlugin implements Plugin<Project> {
+            void apply(final Project project) {}
+
+            static class Rules extends RuleSource {
+                @BinaryTasks
+                void createSampleComponentComponents(ModelMap<Task> tasks, SampleBinary binary) {
                     tasks.create("\${binary.name}Task")
                 }
             }
@@ -131,7 +149,7 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
 
             static class Rules extends RuleSource {
                 @BinaryTasks
-                void createSampleComponentComponents(CollectionBuilder<Task> tasks, SampleBinary binary) {
+                void createSampleComponentComponents(ModelMap<Task> tasks, SampleBinary binary) {
                     tasks.create("\${binary.name}Task", BinaryCreationTask) {
                         println "configuring \${binary.getName()}"
                         it.binary = binary
@@ -165,12 +183,12 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @ComponentBinaries
-                void createBinariesForSampleLibrary(CollectionBuilder<OtherBinary> binaries, SampleLibrary library) {
+                void createBinariesForSampleLibrary(ModelMap<OtherBinary> binaries, SampleLibrary library) {
                     binaries.create("\${library.name}OtherBinary")
                 }
 
                 @BinaryTasks
-                void createTasks(CollectionBuilder<Task> tasks, OtherBinary binary) {
+                void createTasks(ModelMap<Task> tasks, OtherBinary binary) {
                     tasks.create("\${binary.name}OtherTask")
                 }
             }
@@ -208,7 +226,7 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @BinaryTasks
-                void createTasks(CollectionBuilder<Task> tasks, $ruleInputs) {
+                void createTasks(ModelMap<Task> tasks, $ruleInputs) {
                     model.values.each { postFix ->
                         tasks.create("\${binary.name}\${postFix}");
                     }
@@ -243,7 +261,7 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
 
             static class Rules extends RuleSource {
                 @BinaryTasks
-                void createTasks(CollectionBuilder<Task> tasks, SampleBinary binary) {
+                void createTasks(ModelMap<Task> tasks, SampleBinary binary) {
                     tasks.create("\${binary.name}TaskOne"){
                         it.doLast{
                             println "running \${it.name}"
@@ -275,4 +293,4 @@ public class CustomBinaryTasksIntegrationTest extends AbstractIntegrationSpec {
     }
 
 
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentBinariesIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentBinariesIntegrationTest.groovy
index 76a3bca..09e3082 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentBinariesIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentBinariesIntegrationTest.groovy
@@ -15,7 +15,11 @@
  */
 
 package org.gradle.language.base
+
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.model.ModelMap
+import org.gradle.model.collection.CollectionBuilder
+import spock.lang.Ignore
 import spock.lang.Unroll
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
@@ -24,13 +28,6 @@ class CustomComponentBinariesIntegrationTest extends AbstractIntegrationSpec {
 
     def "setup"() {
         buildFile << """
-import org.gradle.model.*
-import org.gradle.model.collection.*
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.internal.reflect.Instantiator
-import javax.inject.Inject
-import org.gradle.internal.service.ServiceRegistry
-
 interface SampleBinary extends BinarySpec {}
 interface OtherSampleBinary extends SampleBinary {}
 
@@ -56,14 +53,12 @@ class DefaultSampleLibrary extends BaseComponentSpec implements SampleLibrary {}
             }
 
             @Mutate
-            void createSampleComponentComponents(CollectionBuilder<SampleLibrary> componentSpecs, ServiceRegistry serviceRegistry) {
-                componentSpecs.create("sampleLib", new Action<SampleLibrary>() {
-                    public void execute(SampleLibrary library) {
-                        library.sources {
-                            it.add(BaseLanguageSourceSet.create(DefaultLibrarySourceSet, "librarySource", "librarySource", serviceRegistry.get(FileResolver), serviceRegistry.get(Instantiator)))
-                        }
+            void createSampleComponentComponents(ModelMap<SampleLibrary> componentSpecs) {
+                componentSpecs.create("sampleLib") {
+                    sources {
+                        librarySource(LibrarySourceSet)
                     }
-                });
+                }
             }
 
             @BinaryType
@@ -75,6 +70,12 @@ class DefaultSampleLibrary extends BaseComponentSpec implements SampleLibrary {}
             void registerOther(BinaryTypeBuilder<OtherSampleBinary> builder) {
                 builder.defaultImplementation(OtherSampleBinaryImpl)
             }
+
+            @LanguageType
+            void registerSourceSet(LanguageTypeBuilder<LibrarySourceSet> builder) {
+                builder.setLanguageName("librarySource")
+                builder.defaultImplementation(DefaultLibrarySourceSet)
+            }
         }
     }
 
@@ -82,9 +83,10 @@ class DefaultSampleLibrary extends BaseComponentSpec implements SampleLibrary {}
 """
     }
 
-    def "can register binaries using @ComponentBinaries"() {
+    @Unroll
+    def "can register binaries using @ComponentBinaries when viewing binaries container as #binariesContainerType.simpleName"() {
         when:
-        buildFile << withSimpleComponentBinaries()
+        buildFile << withSimpleComponentBinaries(binariesContainerType)
         buildFile << """
 
 
@@ -100,6 +102,9 @@ class DefaultSampleLibrary extends BaseComponentSpec implements SampleLibrary {}
 """
         then:
         succeeds "checkModel"
+
+        where:
+        binariesContainerType << [CollectionBuilder, ModelMap]
     }
 
     def "links binaries to component"() {
@@ -113,8 +118,8 @@ class DefaultSampleLibrary extends BaseComponentSpec implements SampleLibrary {}
 --------------------------------
 
 Source sets
-    DefaultLibrarySourceSet 'librarySource:librarySource'
-        src${File.separator}sampleLib${File.separator}librarySource
+    DefaultLibrarySourceSet 'sampleLib:librarySource'
+        srcDir: src${File.separator}sampleLib${File.separator}librarySource
 
 Binaries
     DefaultSampleBinary 'sampleLibBinary'
@@ -129,12 +134,12 @@ Binaries
         buildFile << withSimpleComponentBinaries()
         buildFile << """
         task checkSourceSets << {
-            def sampleBinary = project.binaries.sampleLibBinary
-            def othersSampleBinary = project.binaries.sampleLibOtherBinary
-            assert sampleBinary.source[0] instanceof DefaultLibrarySourceSet
-            assert sampleBinary.source[0].displayName == "DefaultLibrarySourceSet 'librarySource:librarySource'"
-            assert othersSampleBinary.source[0] instanceof DefaultLibrarySourceSet
-            assert othersSampleBinary.source[0].displayName == "DefaultLibrarySourceSet 'librarySource:librarySource'"
+            def sampleBinarySourceSet = project.binaries.sampleLibBinary.source.toList()[0]
+            def othersSampleBinarySourceSet = project.binaries.sampleLibOtherBinary.source.toList()[0]
+            assert sampleBinarySourceSet instanceof DefaultLibrarySourceSet
+            assert sampleBinarySourceSet.displayName == "DefaultLibrarySourceSet 'sampleLib:librarySource'"
+            assert othersSampleBinarySourceSet instanceof DefaultLibrarySourceSet
+            assert othersSampleBinarySourceSet.displayName == "DefaultLibrarySourceSet 'sampleLib:librarySource'"
         }
 """
         then:
@@ -186,10 +191,9 @@ Binaries
                }
 
                @ComponentBinaries
-               void createBinariesForSampleLibrary(CollectionBuilder<SampleBinary> binaries, $ruleInputs) {
+               void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, $ruleInputs) {
                    myModel.values.each{ value ->
                         binaries.create("\${library.name}\${value}Binary")
-
                    }
                }
            }
@@ -212,8 +216,8 @@ DefaultSampleLibrary 'sampleLib'
 --------------------------------
 
 Source sets
-    DefaultLibrarySourceSet 'librarySource:librarySource'
-        src${File.separator}sampleLib${File.separator}librarySource
+    DefaultLibrarySourceSet 'sampleLib:librarySource'
+        srcDir: src${File.separator}sampleLib${File.separator}librarySource
 
 Binaries
     DefaultSampleBinary 'sampleLib1stBinary'
@@ -225,14 +229,14 @@ Binaries
         ruleInputs << ["SampleLibrary library, CustomModel myModel"]//,  "CustomModel myModel, SampleLibrary library"]
     }
 
-    String withSimpleComponentBinaries() {
+    String withSimpleComponentBinaries(Class<? extends CollectionBuilder> binariesContainerType = ModelMap) {
         """
          class MyComponentBinariesPlugin implements Plugin<Project> {
             void apply(final Project project) {}
 
             static class Rules extends RuleSource {
                 @ComponentBinaries
-                void createBinariesForSampleLibrary(CollectionBuilder<SampleBinary> binaries, SampleLibrary library) {
+                void createBinariesForSampleLibrary(${binariesContainerType.simpleName}<SampleBinary> binaries, SampleLibrary library) {
                     binaries.create("\${library.name}Binary")
                     binaries.create("\${library.name}OtherBinary", OtherSampleBinary)
                 }
@@ -241,4 +245,60 @@ Binaries
         apply plugin: MyComponentBinariesPlugin
 """
     }
+
+    def "subject of @ComponentBinaries rule is Groovy decorated"() {
+        buildFile << """
+            class GroovyComponentBinariesRules extends RuleSource {
+                @ComponentBinaries
+                void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleLibrary library) {
+                    binaries.derivedFromMethodName(SampleBinary) {}
+                }
+            }
+
+            apply type: GroovyComponentBinariesRules
+        """
+
+        when:
+        succeeds "components"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+Binaries
+    DefaultSampleBinary 'derivedFromMethodName'
+        build using task: :derivedFromMethodName
+"""))
+    }
+
+    @Ignore("Not supported due to BinaryTasks rules now operating directly on component.binaries, which is not managed - LD - 15/5/15")
+    def "attempt to mutate the subject of a @ComponentBinaries after the method has finished results in an error"() {
+        buildFile << """
+            class BinariesHolder {
+                ModelMap<SampleBinary> binaries
+            }
+            class IllegallyMutatingComponentBinariesRules extends RuleSource {
+                @Model
+                BinariesHolder holder() {
+                    return new BinariesHolder()
+                }
+
+                @ComponentBinaries
+                void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleLibrary library, BinariesHolder holder) {
+                    holder.binaries = binaries
+                }
+
+                @Mutate
+                void mutateBinariesOutsideOfComponentBinariesRule(ModelMap<Task> task, BinariesHolder holder) {
+                    holder.binaries.create("illegal", SampleBinary)
+                }
+            }
+
+            apply type: IllegallyMutatingComponentBinariesRules
+        """
+
+        when:
+        fails "tasks"
+
+        then:
+        failure.assertHasCause("Attempt to mutate closed view of model of type '${ModelMap.name}<SampleBinary>' given to rule 'IllegallyMutatingComponentBinariesRules#createBinariesForSampleLibrary(org.gradle.model.ModelMap<SampleBinary>, SampleLibrary, BinariesHolder)'")
+    }
 }
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentPluginIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentPluginIntegrationTest.groovy
index 14c66a7..19da53c 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentPluginIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/CustomComponentPluginIntegrationTest.groovy
@@ -17,14 +17,13 @@
 package org.gradle.language.base
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.util.TextUtil
 
 class CustomComponentPluginIntegrationTest extends AbstractIntegrationSpec {
     def "setup"() {
+        EnableModelDsl.enable(executer)
         buildFile << """
-import org.gradle.model.*
-import org.gradle.model.collection.*
-
 interface SampleComponent extends ComponentSpec {
     String getVersion()
     void setVersion(String version)
@@ -41,13 +40,20 @@ class DefaultSampleComponent extends BaseComponentSpec implements SampleComponen
 
         and:
         buildFile << """
-task checkModel << {
-    assert project.componentSpecs.size() == 1
-    def sampleLib = project.componentSpecs.sampleLib
-    assert sampleLib instanceof SampleComponent
-    assert sampleLib.projectPath == project.path
-    assert sampleLib.displayName == "DefaultSampleComponent 'sampleLib'"
-    assert sampleLib.version == null
+model {
+    tasks {
+        create("checkModel") {
+            def components = \$("components")
+            doLast {
+                assert components.size() == 1
+                def sampleLib = components.sampleLib
+                assert sampleLib instanceof SampleComponent
+                assert sampleLib.projectPath == project.path
+                assert sampleLib.displayName == "DefaultSampleComponent 'sampleLib'"
+                assert sampleLib.version == null
+            }
+        }
+    }
 }
 """
         then:
@@ -66,10 +72,13 @@ model {
             version = '12'
         }
     }
-}
-task checkModel << {
-    def sampleLib = project.componentSpecs.sampleLib
-    assert sampleLib.version == '12'
+    tasks {
+        create("checkModel") {
+            doLast {
+                assert \$("components").sampleLib.version == '12'
+            }
+        }
+    }
 }
 """
 
@@ -87,7 +96,7 @@ task checkModel << {
                 }
 
                 @Mutate
-                void createSampleComponentComponents(CollectionBuilder<SampleComponent> componentSpecs) {
+                void createSampleComponentComponents(ModelMap<SampleComponent> componentSpecs) {
                     componentSpecs.afterEach {
                         version += ".1"
                     }
@@ -102,11 +111,13 @@ task checkModel << {
                         version = '12'
                     }
                 }
-            }
-
-            task checkModel << {
-                def sampleLib = project.componentSpecs.sampleLib
-                assert sampleLib.version == '12.1'
+                tasks {
+                    create("checkModel") {
+                        doLast {
+                            assert \$("components").sampleLib.version == '12.1'
+                        }
+                    }
+                }
             }
 """
 
@@ -126,8 +137,14 @@ task checkModel << {
 
             apply plugin:MySamplePlugin
 
-            task checkModel << {
-                assert project.componentSpecs.size() == 0
+            model {
+                tasks {
+                    create("checkModel") {
+                        doLast {
+                            assert \$("components").size() == 0
+                        }
+                    }
+                }
             }
 """
 
@@ -180,7 +197,7 @@ BUILD SUCCESSFUL"""))
 
                 static class Rules extends RuleSource {
                     @Mutate
-                    void createSampleComponentComponents(CollectionBuilder<SampleComponent> componentSpecs) {
+                    void createSampleComponentComponents(ModelMap<SampleComponent> componentSpecs) {
                         componentSpecs.create("sampleLib")
                     }
                 }
@@ -188,12 +205,19 @@ BUILD SUCCESSFUL"""))
 
             apply plugin:MyComponentCreationPlugin
 
-            task checkModel << {
-                 assert project.componentSpecs.size() == 1
-                 def sampleLib = project.componentSpecs.sampleLib
-                 assert sampleLib instanceof SampleComponent
-                 assert sampleLib.projectPath == project.path
-                 assert sampleLib.displayName == "DefaultSampleComponent 'sampleLib'"
+            model {
+                tasks {
+                    create("checkModel") {
+                        def components = \$("components")
+                        doLast {
+                            assert components.size() == 1
+                            def sampleLib = components.sampleLib
+                            assert sampleLib instanceof SampleComponent
+                            assert sampleLib.projectPath == project.path
+                            assert sampleLib.displayName == "DefaultSampleComponent 'sampleLib'"
+                        }
+                    }
+                }
             }
 """
 
@@ -219,30 +243,37 @@ BUILD SUCCESSFUL"""))
                 }
 
                 @Mutate
-                void createSampleComponentInstances(CollectionBuilder<SampleComponent> componentSpecs) {
+                void createSampleComponentInstances(ModelMap<SampleComponent> componentSpecs) {
                     componentSpecs.create("sampleComponent")
                 }
 
                 @Mutate
-                void createSampleLibraryInstances(CollectionBuilder<SampleLibrary> componentSpecs) {
+                void createSampleLibraryInstances(ModelMap<SampleLibrary> componentSpecs) {
                     componentSpecs.create("sampleLib")
                 }
             }
 
             apply plugin:MySamplePlugin
 
-            task checkModel << {
-                 assert project.componentSpecs.size() == 2
-
-                 def sampleComponent = project.componentSpecs.sampleComponent
-                 assert sampleComponent instanceof SampleComponent
-                 assert sampleComponent.projectPath == project.path
-                 assert sampleComponent.displayName == "DefaultSampleComponent 'sampleComponent'"
-
-                 def sampleLib = project.componentSpecs.sampleLib
-                 assert sampleLib instanceof SampleLibrary
-                 assert sampleLib.projectPath == project.path
-                 assert sampleLib.displayName == "DefaultSampleLibrary 'sampleLib'"
+            model {
+                tasks {
+                    create("checkModel") {
+                        def components = \$("components")
+                        doLast {
+                            assert components.size() == 2
+
+                            def sampleComponent = components.sampleComponent
+                            assert sampleComponent instanceof SampleComponent
+                            assert sampleComponent.projectPath == project.path
+                            assert sampleComponent.displayName == "DefaultSampleComponent 'sampleComponent'"
+
+                            def sampleLib = components.sampleLib
+                            assert sampleLib instanceof SampleLibrary
+                            assert sampleLib.projectPath == project.path
+                            assert sampleLib.displayName == "DefaultSampleLibrary 'sampleLib'"
+                        }
+                    }
+                }
             }
 """
 
@@ -307,7 +338,7 @@ BUILD SUCCESSFUL"""))
                     builder.defaultImplementation(DefaultSampleComponent)
                 }
                 @Mutate
-                void createSampleComponentComponents(CollectionBuilder<SampleComponent> componentSpecs) {
+                void createSampleComponentComponents(ModelMap<SampleComponent> componentSpecs) {
                     componentSpecs.create("sampleLib")
                 }
             }
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeIntegrationTest.groovy
index 695d500..e488dee 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeIntegrationTest.groovy
@@ -64,9 +64,6 @@ model {
     def "can add custom language sourceSet to component"() {
         when:
         buildFile << """
-        import org.gradle.model.*
-        import org.gradle.model.collection.*
-
         interface SampleComponent extends ComponentSpec {}
         class DefaultSampleComponent extends BaseComponentSpec implements SampleComponent {}
 
@@ -78,7 +75,7 @@ model {
             }
 
             @Mutate
-            void createSampleComponentComponents(CollectionBuilder<SampleComponent> componentSpecs) {
+            void createSampleComponentComponents(ModelMap<SampleComponent> componentSpecs) {
                 componentSpecs.create("main")
             }
         }
@@ -105,7 +102,7 @@ DefaultSampleComponent 'main'
 
 Source sets
     DefaultCustomLanguageSourceSet 'main:custom'
-        src${File.separator}main${File.separator}custom
+        srcDir: src${File.separator}main${File.separator}custom
 """))
     }
 
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeSampleIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeSampleIntegrationTest.groovy
index e0bece0..e33f85f 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeSampleIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/LanguageTypeSampleIntegrationTest.groovy
@@ -41,7 +41,7 @@ DefaultDocumentationComponent 'docs'
 
 Source sets
     DefaultMarkdownSourceSet 'docs:userguide'
-        src${File.separator}docs${File.separator}userguide
+        srcDir: src${File.separator}docs${File.separator}userguide
 
 Binaries
     DefaultDocumentationBinary 'docsBinary'
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..f336402
--- /dev/null
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginIntegrationTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.integtests.fixtures.AbstractIntegrationSpec
+
+class ComponentModelBasePluginIntegrationTest extends AbstractIntegrationSpec {
+
+    def "creates default source set for component"() {
+        given:
+        buildFile << '''
+            import org.gradle.api.NamedDomainObjectFactory
+            import org.gradle.language.base.LanguageSourceSet
+            import org.gradle.language.base.internal.SourceTransformTaskConfig
+            import org.gradle.language.base.internal.registry.LanguageRegistration
+            import org.gradle.language.base.internal.registry.LanguageTransform
+            import org.gradle.language.base.sources.BaseLanguageSourceSet
+            import org.gradle.platform.base.BinarySpec
+            import org.gradle.platform.base.internal.*
+            import org.gradle.platform.base.TransformationFileType
+            import org.gradle.language.base.internal.registry.*
+            import org.gradle.internal.reflect.*
+
+            class TestComponent extends BaseComponentSpec {
+                public Set<Class<? extends TransformationFileType>> getInputTypes() {
+                    [TestTransformationFileType]
+                }
+            }
+
+            class Rules extends RuleSource {
+                @ComponentType
+                void registerComponentType(ComponentTypeBuilder<ComponentSpecInternal> builder) {
+                    builder.defaultImplementation(TestComponent)
+                }
+
+                @Mutate
+                void registerLanguage(LanguageRegistry registry) {
+                    registry.add(new TestLanguageRegistration())
+                }
+
+                @Mutate
+                void registerLanguageTransformation(LanguageTransformContainer transforms) {
+                    transforms.add(new TestLanguageTransformation())
+                }
+
+                @Mutate
+                void addComponent(ComponentSpecContainer components) {
+                    components.create("test", ComponentSpecInternal)
+                }
+
+                @Mutate
+                void addValidateTask(ModelMap<Task> tasks, ComponentSpecContainer components) {
+                    tasks.create("validate") {
+                        doLast {
+                            println "components.test.sources.test: ${components.test.sources.test.getClass().simpleName}"
+                        }
+                    }
+                }
+            }
+
+            class TestLanguageRegistration implements LanguageRegistration {
+                String getName() {
+                    "test"
+                }
+
+                Class getSourceSetType() {
+                    TestSourceSet
+                }
+
+                NamedDomainObjectFactory getSourceSetFactory(String parentName) {
+                    { name -> BaseLanguageSourceSet.create(TestSourceSetImplementation, name, parentName, null, new DirectInstantiator()) }
+                }
+            }
+
+            class TestLanguageTransformation implements LanguageTransform {
+                Class getSourceSetType() {
+                    TestSourceSet
+                }
+
+                Class getOutputType() {
+                    TestTransformationFileType
+                }
+
+                Map<String, Class<?>> getBinaryTools() {
+                    throw new UnsupportedOperationException()
+                }
+
+                SourceTransformTaskConfig getTransformTask() {
+                    throw new UnsupportedOperationException()
+                }
+
+                boolean applyToBinary(BinarySpec binary) {
+                    throw new UnsupportedOperationException()
+                }
+            }
+
+            class TestTransformationFileType implements TransformationFileType {
+            }
+
+
+            interface TestSourceSet extends LanguageSourceSet {
+            }
+
+            class TestSourceSetImplementation extends BaseLanguageSourceSet implements TestSourceSet {
+            }
+
+            apply type: Rules
+        '''
+
+        when:
+        succeeds "validate"
+
+        then:
+        output.contains("components.test.sources.test: TestSourceSetImplementation")
+    }
+}
diff --git a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/LifecycleBasePluginIntegrationTest.groovy b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/LifecycleBasePluginIntegrationTest.groovy
index d18e10b..10c0f13 100644
--- a/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/LifecycleBasePluginIntegrationTest.groovy
+++ b/subprojects/platform-base/src/integTest/groovy/org/gradle/language/base/plugins/LifecycleBasePluginIntegrationTest.groovy
@@ -60,7 +60,7 @@ class LifecycleBasePluginIntegrationTest extends AbstractIntegrationSpec {
 
     def "binaries are built when build task execution is requested"() {
         buildFile << """
-            import org.gradle.model.collection.CollectionBuilder
+            import org.gradle.model.ModelMap
 
             interface SampleBinary extends BinarySpec {
             }
@@ -75,7 +75,7 @@ class LifecycleBasePluginIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 @Mutate
-                void createSampleBinary(CollectionBuilder<SampleBinary> binarySpecs) {
+                void createSampleBinary(ModelMap<SampleBinary> binarySpecs) {
                     binarySpecs.create("sampleBinary")
                 }
             }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/FunctionalSourceSet.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/FunctionalSourceSet.java
index ac58bc4..e72d133 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/FunctionalSourceSet.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/FunctionalSourceSet.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.language.base;
 
-import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
 import org.gradle.api.Incubating;
 import org.gradle.api.Named;
+import org.gradle.api.internal.ExtensiblePolymorphicDomainObjectContainerInternal;
 
 /**
  * A container holding {@link LanguageSourceSet}s with a similar function
@@ -25,6 +25,6 @@ import org.gradle.api.Named;
  */
 @Incubating
 // TODO:DAZ Make this internal
-public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainer<LanguageSourceSet>, Named {
+public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainerInternal<LanguageSourceSet>, Named {
     FunctionalSourceSet copy(String name);
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/LanguageSourceSet.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/LanguageSourceSet.java
index 6dd3f18..31a90b1 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/LanguageSourceSet.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/LanguageSourceSet.java
@@ -15,12 +15,8 @@
  */
 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.*;
 import org.gradle.api.file.SourceDirectorySet;
-import org.gradle.api.BuildableModelElement;
 import org.gradle.internal.HasInternalProtocol;
 
 /**
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java
index eb33b44..d99d3a2 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DefaultFunctionalSourceSet.java
@@ -16,14 +16,13 @@
 package org.gradle.language.base.internal;
 
 import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectFactory;
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.api.internal.rules.AddOnlyRuleAwarePolymorphicDomainObjectContainer;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.language.base.FunctionalSourceSet;
 import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.language.base.ProjectSourceSet;
 
-public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectContainer<LanguageSourceSet> implements FunctionalSourceSet {
+public class DefaultFunctionalSourceSet extends AddOnlyRuleAwarePolymorphicDomainObjectContainer<LanguageSourceSet> implements FunctionalSourceSet {
     private final String name;
     private final ProjectSourceSet projectSourceSet;
 
@@ -51,16 +50,8 @@ public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectCo
     // TODO:DAZ Perhaps we should pull out a LanguageSourceSet 'factory-for-type' so we only register the languages once
     public FunctionalSourceSet copy(String name) {
         DefaultFunctionalSourceSet copy = getInstantiator().newInstance(DefaultFunctionalSourceSet.class, name, getInstantiator(), projectSourceSet);
-        for (Class<? extends LanguageSourceSet> languageType : factories.keySet()) {
-            copyFactory(copy, languageType);
-        }
+        copy.namedEntityInstantiator.copyFactoriesFrom(namedEntityInstantiator);
         copy.addAll(this);
         return copy;
     }
-
-    <T extends LanguageSourceSet, U extends T> void copyFactory(DefaultFunctionalSourceSet target, Class<T> type) {
-        @SuppressWarnings("unchecked")
-        NamedDomainObjectFactory<U> factory = (NamedDomainObjectFactory<U>) factories.get(type);
-        target.registerFactory(type, factory);
-    }
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DependentSourceSetInternal.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DependentSourceSetInternal.java
new file mode 100644
index 0000000..8c75c9b
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/DependentSourceSetInternal.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Action;
+import org.gradle.platform.base.DependencySpecContainer;
+
+public interface DependentSourceSetInternal {
+    DependencySpecContainer getDependencies();
+
+    DependencySpecContainer dependencies(Action<? super DependencySpecContainer> configureAction);
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetContainer.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetContainer.java
index 0a42f35..b5fdc3c 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetContainer.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetContainer.java
@@ -16,8 +16,8 @@
 
 package org.gradle.language.base.internal;
 
-import com.google.common.collect.Sets;
 import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.CompositeDomainObjectSet;
 import org.gradle.api.internal.DefaultDomainObjectSet;
 import org.gradle.internal.typeconversion.NotationParser;
 import org.gradle.language.base.FunctionalSourceSet;
@@ -28,7 +28,7 @@ import java.util.Set;
 public class LanguageSourceSetContainer {
     private final NotationParser<Object, Set<LanguageSourceSet>> sourcesNotationParser = SourceSetNotationParser.parser();
     private FunctionalSourceSet mainSources;
-    private Set<LanguageSourceSet> additionalSources = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
+    private DefaultDomainObjectSet<LanguageSourceSet> additionalSources = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
 
     public void setMainSources(FunctionalSourceSet mainSources) {
         this.mainSources = mainSources;
@@ -43,9 +43,8 @@ public class LanguageSourceSetContainer {
     }
 
     public DomainObjectSet<LanguageSourceSet> getSources() {
-        Set<LanguageSourceSet> all = Sets.newLinkedHashSet();
-        all.addAll(mainSources);
-        all.addAll(additionalSources);
-        return new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class, all);
+        @SuppressWarnings("unchecked")
+        DomainObjectSet<LanguageSourceSet> sources = CompositeDomainObjectSet.create(LanguageSourceSet.class, mainSources, additionalSources);
+        return sources;
     }
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetInternal.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetInternal.java
index 6a2cd81..6f77dd2 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetInternal.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/LanguageSourceSetInternal.java
@@ -17,9 +17,12 @@ package org.gradle.language.base.internal;
 
 import org.gradle.api.Task;
 import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.model.internal.type.ModelType;
 
 public interface LanguageSourceSetInternal extends LanguageSourceSet {
 
+    ModelType<LanguageSourceSet> PUBLIC_MODEL_TYPE = ModelType.of(LanguageSourceSet.class);
+
     /**
      * A unique name for this source set across all functional source sets.
      */
@@ -31,4 +34,5 @@ public interface LanguageSourceSetInternal extends LanguageSourceSet {
     boolean getMayHaveSources();
 
     Task getGeneratorTask();
+
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySpecFactoryRegistry.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySpecFactoryRegistry.java
new file mode 100644
index 0000000..0146423
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySpecFactoryRegistry.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model;
+
+import com.google.common.collect.Maps;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.rules.NamedDomainObjectFactoryRegistry;
+import org.gradle.api.internal.rules.RuleAwareNamedDomainObjectFactoryRegistry;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.platform.base.BinarySpec;
+import org.gradle.api.internal.rules.DefaultRuleAwareNamedDomainObjectFactoryRegistry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+public class BinarySpecFactoryRegistry implements RuleAwareNamedDomainObjectFactoryRegistry<BinarySpec> {
+
+    private final CollectingNamedBinarySpecFactoryRegistry collector = new CollectingNamedBinarySpecFactoryRegistry();
+
+    private final RuleAwareNamedDomainObjectFactoryRegistry<BinarySpec> delegate = new DefaultRuleAwareNamedDomainObjectFactoryRegistry<BinarySpec>(collector);
+
+    @Override
+    public <U extends BinarySpec> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
+        delegate.registerFactory(type, factory, descriptor);
+    }
+
+    @Override
+    public <U extends BinarySpec> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+        delegate.registerFactory(type, factory);
+    }
+
+    public void copyInto(NamedDomainObjectFactoryRegistry<BinarySpec> destination) {
+        for (Map.Entry<Class<BinarySpec>, NamedDomainObjectFactory<? extends BinarySpec>> factory : collector.factories.entrySet()) {
+            destination.registerFactory(factory.getKey(), factory.getValue());
+        }
+    }
+
+    private static class CollectingNamedBinarySpecFactoryRegistry implements NamedDomainObjectFactoryRegistry<BinarySpec> {
+
+        private final HashMap<Class<BinarySpec>, NamedDomainObjectFactory<? extends BinarySpec>> factories = Maps.newHashMap();
+
+        @Override
+        public <U extends BinarySpec> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+            Class<BinarySpec> o = uncheckedCast(type);
+            factories.put(o, factory);
+        }
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentRules.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentRules.java
new file mode 100644
index 0000000..9951efa
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentRules.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.registry.LanguageRegistration;
+import org.gradle.language.base.internal.registry.LanguageRegistry;
+import org.gradle.language.base.internal.registry.LanguageTransform;
+import org.gradle.language.base.internal.registry.LanguageTransformContainer;
+import org.gradle.model.Defaults;
+import org.gradle.model.RuleSource;
+import org.gradle.platform.base.ComponentSpec;
+import org.gradle.platform.base.internal.ComponentSpecInternal;
+
+/**
+ * Cross-cutting rules for all {@link org.gradle.platform.base.ComponentSpec} instances.
+ */
+ at SuppressWarnings("UnusedDeclaration")
+public class ComponentRules extends RuleSource {
+    @Defaults
+    void initializeSourceSets(ComponentSpec component, LanguageRegistry languageRegistry, LanguageTransformContainer languageTransforms) {
+        for (LanguageRegistration<?> languageRegistration : languageRegistry) {
+            // TODO - allow view as internal type and remove the cast
+            ComponentSourcesRegistrationAction.create(languageRegistration, languageTransforms).execute((ComponentSpecInternal) component);
+        }
+    }
+
+    @Defaults
+    void applyDefaultSourceConventions(final ComponentSpec component) {
+        component.getSource().afterEach(new AddDefaultSourceLocation(component.getName()));
+    }
+
+    // TODO:DAZ Needs to be a separate action since can't have parameterized utility methods in a RuleSource
+    private static class ComponentSourcesRegistrationAction<U extends LanguageSourceSet> implements Action<ComponentSpecInternal> {
+        private final LanguageRegistration<U> languageRegistration;
+        private final LanguageTransformContainer languageTransforms;
+
+        private ComponentSourcesRegistrationAction(LanguageRegistration<U> registration, LanguageTransformContainer languageTransforms) {
+            this.languageRegistration = registration;
+            this.languageTransforms = languageTransforms;
+        }
+
+        public static <U extends LanguageSourceSet> ComponentSourcesRegistrationAction<U> create(LanguageRegistration<U> registration, LanguageTransformContainer languageTransforms) {
+            return new ComponentSourcesRegistrationAction<U>(registration, languageTransforms);
+        }
+
+        public void execute(ComponentSpecInternal componentSpecInternal) {
+            registerLanguageSourceSetFactory(componentSpecInternal);
+            createDefaultSourceSetForComponents(componentSpecInternal);
+        }
+
+        void registerLanguageSourceSetFactory(final ComponentSpecInternal component) {
+            final FunctionalSourceSet functionalSourceSet = component.getSources();
+            NamedDomainObjectFactory<? extends U> sourceSetFactory = languageRegistration.getSourceSetFactory(functionalSourceSet.getName());
+            functionalSourceSet.registerFactory(languageRegistration.getSourceSetType(), sourceSetFactory);
+        }
+
+        // If there is a transform for the language into one of the component inputs, add a default source set
+        void createDefaultSourceSetForComponents(final ComponentSpecInternal component) {
+            final FunctionalSourceSet functionalSourceSet = component.getSources();
+            for (LanguageTransform<?, ?> languageTransform : languageTransforms) {
+                if (languageTransform.getSourceSetType().equals(languageRegistration.getSourceSetType())
+                        && component.getInputTypes().contains(languageTransform.getOutputType())) {
+                    functionalSourceSet.maybeCreate(languageRegistration.getName(), languageRegistration.getSourceSetType());
+                    return;
+                }
+            }
+        }
+    }
+
+    private static class AddDefaultSourceLocation implements Action<LanguageSourceSet> {
+        private String name;
+
+        public AddDefaultSourceLocation(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public void execute(LanguageSourceSet languageSourceSet) {
+            // Only apply default locations when none explicitly configured
+            final SourceDirectorySet source = languageSourceSet.getSource();
+            if (source.getSrcDirs().isEmpty()) {
+                source.srcDir(String.format("src/%s/%s", name, languageSourceSet.getName()));
+            }
+        }
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentSpecInitializer.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentSpecInitializer.java
new file mode 100644
index 0000000..9765f8a
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/ComponentSpecInitializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model;
+
+import org.gradle.api.Action;
+import org.gradle.internal.BiAction;
+import org.gradle.model.internal.core.ModelCreator;
+import org.gradle.model.internal.core.ModelCreators;
+import org.gradle.model.internal.core.MutableModelNode;
+import org.gradle.model.internal.core.UnmanagedModelProjection;
+import org.gradle.model.internal.core.rule.describe.StandardDescriptorFactory;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.platform.base.BinarySpec;
+import org.gradle.platform.base.BinaryTasksCollection;
+
+public class ComponentSpecInitializer {
+
+    private final static BiAction<MutableModelNode, BinarySpec> BINARY_ACTION = new BiAction<MutableModelNode, BinarySpec>() {
+        @Override
+        public void execute(MutableModelNode node, BinarySpec spec) {
+            final ModelType<BinaryTasksCollection> itemType = ModelType.of(BinaryTasksCollection.class);
+            ModelCreator itemCreator = ModelCreators.of(node.getPath().child("tasks"), new Action<MutableModelNode>() {
+                @Override
+                public void execute(MutableModelNode modelNode) {
+                    BinaryTasksCollection tasks = modelNode.getParent().getPrivateData(ModelType.of(BinarySpec.class)).getTasks();
+                    modelNode.setPrivateData(itemType, tasks);
+                }
+            })
+                .withProjection(new UnmanagedModelProjection<BinaryTasksCollection>(itemType))
+                .descriptor(new StandardDescriptorFactory(node.getDescriptor()).transform("tasks"))
+                .build();
+            node.addLink(itemCreator);
+        }
+    };
+
+    public static BiAction<MutableModelNode, BinarySpec> binaryAction() {
+        return BINARY_ACTION;
+    }
+
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/ComponentModelBasePlugin.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/ComponentModelBasePlugin.java
index 2ed6ad8..512f5db 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/ComponentModelBasePlugin.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/ComponentModelBasePlugin.java
@@ -17,26 +17,31 @@ package org.gradle.language.base.plugins;
 
 import org.gradle.api.*;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.rules.ModelMapCreators;
+import org.gradle.api.internal.rules.NamedDomainObjectFactoryRegistry;
 import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.internal.util.BiFunction;
 import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.language.base.ProjectSourceSet;
 import org.gradle.language.base.internal.LanguageSourceSetInternal;
 import org.gradle.language.base.internal.SourceTransformTaskConfig;
+import org.gradle.language.base.internal.model.BinarySpecFactoryRegistry;
+import org.gradle.language.base.internal.model.ComponentRules;
 import org.gradle.language.base.internal.registry.*;
 import org.gradle.model.*;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.collection.internal.BridgedCollections;
+import org.gradle.model.internal.core.ModelCreator;
 import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.MutableModelNode;
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
+import org.gradle.model.internal.manage.schema.ModelMapSchema;
+import org.gradle.model.internal.manage.schema.ModelSchemaStore;
 import org.gradle.model.internal.registry.ModelRegistry;
 import org.gradle.model.internal.type.ModelType;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.ComponentSpec;
-import org.gradle.platform.base.ComponentSpecContainer;
-import org.gradle.platform.base.PlatformContainer;
+import org.gradle.platform.base.*;
 import org.gradle.platform.base.internal.*;
 
 import javax.inject.Inject;
@@ -52,38 +57,42 @@ import static org.apache.commons.lang.StringUtils.capitalize;
  */
 @Incubating
 public class ComponentModelBasePlugin implements Plugin<ProjectInternal> {
-
-    private final Instantiator instantiator;
     private final ModelRegistry modelRegistry;
+    private final ModelSchemaStore schemaStore;
 
     @Inject
-    public ComponentModelBasePlugin(Instantiator instantiator, ModelRegistry modelRegistry) {
-        this.instantiator = instantiator;
+    public ComponentModelBasePlugin(ModelRegistry modelRegistry, ModelSchemaStore schemaStore) {
         this.modelRegistry = modelRegistry;
+        this.schemaStore = schemaStore;
     }
 
     public void apply(final ProjectInternal project) {
         project.getPluginManager().apply(LanguageBasePlugin.class);
 
-        // TODO:DAZ Remove this extension: will first need to change ComponentTypeRuleDefinitionHandler not to access ComponentSpecContainer via extension
-        DefaultComponentSpecContainer components = project.getExtensions().create("componentSpecs", DefaultComponentSpecContainer.class, instantiator);
-        String descriptor = ComponentModelBasePlugin.class.getName() + ".apply()";
-        BridgedCollections.dynamicTypes(
-                modelRegistry,
-                ModelPath.path("components"),
-                descriptor,
-                ModelType.of(DefaultComponentSpecContainer.class),
-                ModelType.of(DefaultComponentSpecContainer.class),
-                ModelType.of(ComponentSpec.class),
-                components,
-                Named.Namer.INSTANCE,
-                BridgedCollections.itemDescriptor(descriptor)
+        SimpleModelRuleDescriptor descriptor = new SimpleModelRuleDescriptor(ComponentModelBasePlugin.class.getName() + ".apply()");
+
+        ModelMapSchema<ComponentSpecContainer> schema = (ModelMapSchema<ComponentSpecContainer>) schemaStore.getSchema(ModelType.of(ComponentSpecContainer.class));
+        ModelPath components = ModelPath.path("components");
+        ModelCreator componentsCreator = ModelMapCreators.specialized(
+            components,
+            ComponentSpec.class,
+            ComponentSpecContainer.class,
+            schema.getManagedImpl().asSubclass(ComponentSpecContainer.class),
+            ModelReference.of(ComponentSpecFactory.class),
+            descriptor
         );
+        modelRegistry.create(componentsCreator);
+        modelRegistry.getRoot().applyToAllLinksTransitive(ModelType.of(ComponentSpec.class), ComponentRules.class);
     }
 
     @SuppressWarnings("UnusedDeclaration")
     static class Rules extends RuleSource {
         @Model
+        ComponentSpecFactory componentSpecFactory() {
+            return new ComponentSpecFactory("this collection");
+        }
+
+        @Model
         LanguageRegistry languages(ServiceRegistry serviceRegistry) {
             return serviceRegistry.get(Instantiator.class).newInstance(DefaultLanguageRegistry.class);
         }
@@ -93,17 +102,9 @@ public class ComponentModelBasePlugin implements Plugin<ProjectInternal> {
             return serviceRegistry.get(Instantiator.class).newInstance(DefaultLanguageTransformContainer.class);
         }
 
-        @Defaults
-        void initializeSourceSetsForComponents(final CollectionBuilder<ComponentSpec> components, LanguageRegistry languageRegistry, LanguageTransformContainer languageTransforms) {
-            for (LanguageRegistration<?> languageRegistration : languageRegistry) {
-                // TODO - allow beforeEach() to be applied to internal types
-                components.beforeEach(ComponentSourcesRegistrationAction.create(languageRegistration, languageTransforms));
-            }
-        }
-
         // Required because creation of Binaries from Components is not yet wired into the infrastructure
         @Mutate
-        void closeComponentsForBinaries(CollectionBuilder<Task> tasks, ComponentSpecContainer components) {
+        void closeComponentsForBinaries(ModelMap<Task> tasks, ComponentSpecContainer components) {
         }
 
         // Finalizing here, as we need this to run after any 'assembling' task (jar, link, etc) is created.
@@ -134,21 +135,6 @@ public class ComponentModelBasePlugin implements Plugin<ProjectInternal> {
             }
         }
 
-        @Mutate
-        void applyDefaultSourceConventions(CollectionBuilder<ComponentSpec> componentSpecs) {
-            componentSpecs.afterEach(new Action<ComponentSpec>() {
-                @Override
-                public void execute(ComponentSpec componentSpec) {
-                    for (LanguageSourceSet languageSourceSet : componentSpec.getSource()) {
-                        // Only apply default locations when none explicitly configured
-                        if (languageSourceSet.getSource().getSrcDirs().isEmpty()) {
-                            languageSourceSet.getSource().srcDir(String.format("src/%s/%s", componentSpec.getName(), languageSourceSet.getName()));
-                        }
-                    }
-                }
-            });
-        }
-
         // TODO:DAZ Work out why this is required
         @Mutate
         void closeSourcesForBinaries(BinaryContainer binaries, ProjectSourceSet sources) {
@@ -172,42 +158,37 @@ public class ComponentModelBasePlugin implements Plugin<ProjectInternal> {
             extensions.add("platforms", platforms);
         }
 
-    }
-
-    // TODO:DAZ Needs to be a separate action since can't have parameterized utility methods in a RuleSource
-    private static class ComponentSourcesRegistrationAction<U extends LanguageSourceSet> implements Action<ComponentSpec> {
-        private final LanguageRegistration<U> languageRegistration;
-        private final LanguageTransformContainer languageTransforms;
-
-        private ComponentSourcesRegistrationAction(LanguageRegistration<U> registration, LanguageTransformContainer languageTransforms) {
-            this.languageRegistration = registration;
-            this.languageTransforms = languageTransforms;
-        }
-
-        public static <U extends LanguageSourceSet> ComponentSourcesRegistrationAction<U> create(LanguageRegistration<U> registration, LanguageTransformContainer languageTransforms) {
-            return new ComponentSourcesRegistrationAction<U>(registration, languageTransforms);
-        }
+        @Model
+        BinarySpecFactory binarySpecFactory(final BinarySpecFactoryRegistry binaryFactoryRegistry) {
+            // BinarySpecFactoryRegistry is used by the BinaryContainer API, which we still need for the time being.
+            // We are adapting it to BinarySpecFactory here so it can be used by component.binaries model maps
 
-        public void execute(ComponentSpec componentSpec) {
-            ComponentSpecInternal componentSpecInternal = (ComponentSpecInternal) componentSpec;
-            registerLanguageSourceSetFactory(componentSpecInternal);
-            createDefaultSourceSetForComponents(componentSpecInternal);
-        }
+            final BinarySpecFactory binarySpecFactory = new BinarySpecFactory("this collection");
+            binaryFactoryRegistry.copyInto(new NamedDomainObjectFactoryRegistry<BinarySpec>() {
+                @Override
+                public <U extends BinarySpec> void registerFactory(Class<U> type, final NamedDomainObjectFactory<? extends U> factory) {
+                    binarySpecFactory.register(type, null, new BiFunction<U, String, MutableModelNode>() {
+                        @Override
+                        public U apply(String s, MutableModelNode modelNode) {
+                            final U binarySpec = factory.create(s);
+                            final Object parentObject = modelNode.getParent().getParent().getPrivateData();
+                            if (parentObject instanceof ComponentSpec && binarySpec instanceof ComponentSpecAware) {
+                                ((ComponentSpecAware) binarySpec).setComponent((ComponentSpec) parentObject);
+                            }
 
-        void registerLanguageSourceSetFactory(final ComponentSpecInternal component) {
-            final FunctionalSourceSet functionalSourceSet = component.getSources();
-            NamedDomainObjectFactory<? extends U> sourceSetFactory = languageRegistration.getSourceSetFactory(functionalSourceSet.getName());
-            functionalSourceSet.registerFactory(languageRegistration.getSourceSetType(), sourceSetFactory);
+                            return binarySpec;
+                        }
+                    });
+                }
+            });
+            return binarySpecFactory;
         }
 
-        // If there is a transform for the language into one of the component inputs, add a default source set
-        void createDefaultSourceSetForComponents(final ComponentSpecInternal component) {
-            final FunctionalSourceSet functionalSourceSet = component.getSources();
-            for (LanguageTransform<?, ?> languageTransform : languageTransforms) {
-                if (languageTransform.getSourceSetType().equals(languageRegistration.getSourceSetType())
-                        && component.getInputTypes().contains(languageTransform.getOutputType())) {
-                    functionalSourceSet.maybeCreate(languageRegistration.getName(), languageRegistration.getSourceSetType());
-                    return;
+        @Defaults
+        void collectBinaries(BinaryContainer binaries, ComponentSpecContainer componentSpecs) {
+            for (ComponentSpec componentSpec : componentSpecs.values()) {
+                for (BinarySpec binary : componentSpec.getBinaries().values()) {
+                    binaries.add(binary);
                 }
             }
         }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LanguageBasePlugin.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LanguageBasePlugin.java
index ac5924d..7a35fb2 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LanguageBasePlugin.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LanguageBasePlugin.java
@@ -22,29 +22,28 @@ import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.BiAction;
-import org.gradle.internal.BiActions;
+import org.gradle.internal.Factories;
+import org.gradle.internal.Transformers;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.text.TreeFormatter;
 import org.gradle.language.base.ProjectSourceSet;
 import org.gradle.language.base.internal.DefaultProjectSourceSet;
-import org.gradle.model.Model;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
+import org.gradle.language.base.internal.model.BinarySpecFactoryRegistry;
+import org.gradle.language.base.internal.model.ComponentSpecInitializer;
+import org.gradle.model.*;
 import org.gradle.model.collection.internal.BridgedCollections;
+import org.gradle.model.collection.internal.PolymorphicModelMapProjection;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
 import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
 import org.gradle.model.internal.registry.ModelRegistry;
 import org.gradle.model.internal.type.ModelType;
-import org.gradle.model.internal.type.ModelTypes;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.platform.base.BinarySpec;
 import org.gradle.platform.base.internal.BinarySpecInternal;
 import org.gradle.platform.base.internal.DefaultBinaryContainer;
 
 import javax.inject.Inject;
-import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -79,22 +78,27 @@ public class LanguageBasePlugin implements Plugin<Project> {
         final String descriptor = LanguageBasePlugin.class.getName() + ".apply()";
         final ModelRuleDescriptor ruleDescriptor = new SimpleModelRuleDescriptor(descriptor);
         ModelPath binariesPath = ModelPath.path("binaries");
-        BridgedCollections.dynamicTypes(
-                modelRegistry,
-                binariesPath,
-                descriptor,
-                ModelType.of(DefaultBinaryContainer.class),
-                ModelType.of(DefaultBinaryContainer.class),
-                ModelType.of(BinarySpec.class),
-                binaries,
+
+        ModelType<BinarySpec> binarySpecModelType = ModelType.of(BinarySpec.class);
+        modelRegistry.createOrReplace(
+            BridgedCollections.creator(
+                ModelReference.of(binariesPath, DefaultBinaryContainer.class),
+                Transformers.constant(binaries),
                 Named.Namer.INSTANCE,
+                descriptor,
                 BridgedCollections.itemDescriptor(descriptor)
+            )
+                .descriptor(descriptor)
+                .ephemeral(true)
+                .withProjection(PolymorphicModelMapProjection.of(binarySpecModelType, NodeBackedModelMap.createUsingParentNode(binarySpecModelType)))
+                .withProjection(UnmanagedModelProjection.of(DefaultBinaryContainer.class))
+                .build()
         );
 
-        modelRegistry.configure(ModelActionRole.Defaults, DirectNodeModelAction.of(ModelReference.of(binariesPath), ruleDescriptor, new Action<MutableModelNode>() {
+        modelRegistry.configure(ModelActionRole.Defaults, DirectNodeNoInputsModelAction.of(ModelReference.of(binariesPath), ruleDescriptor, new Action<MutableModelNode>() {
             @Override
             public void execute(MutableModelNode binariesNode) {
-                binariesNode.applyToAllLinks(ModelActionRole.Finalize, BiActionBackedModelAction.single(ModelReference.of(BinarySpec.class), ruleDescriptor, ModelReference.of(ITaskFactory.class), new BiAction<BinarySpec, ITaskFactory>() {
+                binariesNode.applyToAllLinks(ModelActionRole.Finalize, InputUsingModelAction.single(ModelReference.of(BinarySpec.class), ruleDescriptor, ModelReference.of(ITaskFactory.class), new BiAction<BinarySpec, ITaskFactory>() {
                     @Override
                     public void execute(BinarySpec binary, ITaskFactory taskFactory) {
                         if (!((BinarySpecInternal) binary).isLegacyBinary()) {
@@ -105,26 +109,20 @@ public class LanguageBasePlugin implements Plugin<Project> {
                         }
                     }
                 }));
-
-                binariesNode.applyToAllLinks(ModelActionRole.Initialize, DirectNodeModelAction.of(ModelReference.of(BinarySpec.class), new SimpleModelRuleDescriptor(descriptor + ".tasks"), new Action<MutableModelNode>() {
-                    @Override
-                    public void execute(MutableModelNode modelNode) {
-                        ModelPath binaryPath = modelNode.getPath();
-                        ModelPath taskNodePath = binaryPath.child("__tasks");
-                        ModelType<Collection<Task>> taskCollectionType = ModelTypes.collectionOf(Task.class);
-                        ModelReference<Collection<Task>> tasksNodeReference = ModelReference.of(taskNodePath, taskCollectionType);
-                        modelNode.addLink(ModelCreators.of(tasksNodeReference, BiActions.doNothing())
-                                        .withProjection(new UnmanagedModelProjection<Collection<Task>>(taskCollectionType))
-                                        .descriptor(descriptor + ".createTasksNode")
-                                        .build()
-                        );
-                        MutableModelNode link = modelNode.getLink(taskNodePath.getName());
-                        assert link != null;
-                        link.setPrivateData(taskCollectionType, modelNode.getPrivateData(ModelType.of(BinarySpec.class)).getTasks());
-                    }
-                }));
             }
         }));
+
+        modelRegistry.createOrReplace(ModelCreators.unmanagedInstance(ModelReference.of(ModelPath.path("__binarySpecFactoryRegistry"), ModelType.of(BinarySpecFactoryRegistry.class)), Factories.constant(new BinarySpecFactoryRegistry()))
+            .descriptor(ruleDescriptor)
+            .ephemeral(true)
+            .hidden(true)
+            .build());
+
+        modelRegistry.getRoot().applyToAllLinksTransitive(ModelActionRole.Defaults,
+            DirectNodeNoInputsModelAction.of(
+                ModelReference.of(BinarySpec.class),
+                new SimpleModelRuleDescriptor(descriptor),
+                ComponentSpecInitializer.binaryAction()));
     }
 
     @SuppressWarnings("UnusedDeclaration")
@@ -165,6 +163,11 @@ public class LanguageBasePlugin implements Plugin<Project> {
             }
         }
 
+        @Defaults
+        void registerBinaryFactories(BinaryContainer binaries, BinarySpecFactoryRegistry binaryFactoryRegistry) {
+            binaryFactoryRegistry.copyInto(binaries);
+        }
+
         private static class CheckForNotBuildableBinariesAction implements Action<Task> {
             private final List<BinarySpecInternal> notBuildable;
 
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LifecycleBasePlugin.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LifecycleBasePlugin.java
index 0aa0580..ec2b676 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LifecycleBasePlugin.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/plugins/LifecycleBasePlugin.java
@@ -57,7 +57,7 @@ public class LifecycleBasePlugin implements Plugin<ProjectInternal> {
             @Override
             public void execute(Delete clean) {
                 clean.setDescription("Deletes the build directory.");
-                clean.setGroup(VERIFICATION_GROUP);
+                clean.setGroup(BUILD_GROUP);
                 clean.delete(new Callable<File>() {
                     public File call() throws Exception {
                         return project.getBuildDir();
diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/sources/BaseLanguageSourceSet.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/sources/BaseLanguageSourceSet.java
index dc0dfbf..24b856b 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/language/base/sources/BaseLanguageSourceSet.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/sources/BaseLanguageSourceSet.java
@@ -51,6 +51,10 @@ public abstract class BaseLanguageSourceSet extends AbstractBuildableModelElemen
         return fullName;
     }
 
+    public String getParentName() {
+        return parentName;
+    }
+
     @Override
     public void builtBy(Object... tasks) {
         generated = true;
diff --git a/subprojects/platform-base/src/main/java/org/gradle/model/internal/core/DomainObjectSetBackedModelMap.java b/subprojects/platform-base/src/main/java/org/gradle/model/internal/core/DomainObjectSetBackedModelMap.java
new file mode 100644
index 0000000..82d99c0
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/model/internal/core/DomainObjectSetBackedModelMap.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Namer;
+import org.gradle.api.Nullable;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.Actions;
+import org.gradle.internal.Cast;
+import org.gradle.internal.Specs;
+import org.gradle.model.ModelMap;
+import org.gradle.model.RuleSource;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static org.gradle.internal.Cast.uncheckedCast;
+
+public class DomainObjectSetBackedModelMap<T> implements ModelMap<T> {
+
+    private final Class<T> elementType;
+    private final DomainObjectSet<T> set;
+    private final NamedEntityInstantiator<T> instantiator;
+    private final Namer<? super T> namer;
+    private final Action<? super T> onCreateAction;
+
+    public DomainObjectSetBackedModelMap(Class<T> elementType, DomainObjectSet<T> backingSet, NamedEntityInstantiator<T> instantiator, Namer<? super T> namer, Action<? super T> onCreateAction) {
+        this.elementType = elementType;
+        this.set = backingSet;
+        this.instantiator = instantiator;
+        this.namer = namer;
+        this.onCreateAction = onCreateAction;
+    }
+
+    private <S> ModelMap<S> toNonSubtypeMap(Class<S> type) {
+        DomainObjectSet<S> cast = toNonSubtype(type);
+        Namer<S> castNamer = Cast.uncheckedCast(namer);
+        return DomainObjectSetBackedModelMap.wrap(type, cast, NamedEntityInstantiators.nonSubtype(type, elementType), castNamer, Actions.doNothing());
+    }
+
+    private <S> DomainObjectSet<S> toNonSubtype(final Class<S> type) {
+        return uncheckedCast(set.matching(Specs.isInstance(type)));
+    }
+
+    private <S extends T> ModelMap<S> toSubtypeMap(Class<S> itemSubtype) {
+        NamedEntityInstantiator<S> instantiator = uncheckedCast(this.instantiator);
+        return DomainObjectSetBackedModelMap.wrap(itemSubtype, set.withType(itemSubtype), instantiator, namer, onCreateAction);
+    }
+
+    @Nullable
+    @Override
+    public T get(String name) {
+        return Iterables.find(set, new HasNamePredicate<T>(name, namer), null);
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return Sets.newHashSet(Iterables.transform(set, new ToName<T>(namer)));
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Action<? super S> configAction) {
+        toNonSubtype(type).all(configAction);
+    }
+
+    private static class HasNamePredicate<T> implements Predicate<T> {
+        private final String name;
+        private final Namer<? super T> namer;
+
+        public HasNamePredicate(String name, Namer<? super T> namer) {
+            this.name = name;
+            this.namer = namer;
+        }
+
+        @Override
+        public boolean apply(@Nullable T input) {
+            return namer.determineName(input).equals(name);
+        }
+    }
+
+    private static class ToName<T> implements Function<T, String> {
+        private final Namer<? super T> namer;
+
+        public ToName(Namer<? super T> namer) {
+            this.namer = namer;
+        }
+
+        @Override
+        public String apply(@Nullable T input) {
+            return namer.determineName(input);
+        }
+    }
+
+    public static <T> DomainObjectSetBackedModelMap<T> wrap(Class<T> elementType, DomainObjectSet<T> domainObjectSet, NamedEntityInstantiator<T> instantiator, Namer<? super T> namer, Action<? super T> onCreate) {
+        return new DomainObjectSetBackedModelMap<T>(elementType, domainObjectSet, instantiator, namer, onCreate);
+    }
+
+    @Override
+    public int size() {
+        return set.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return set.isEmpty();
+    }
+
+    @Nullable
+    @Override
+    public T get(Object name) {
+        return get(name.toString());
+    }
+
+    @Override
+    public boolean containsKey(Object name) {
+        return keySet().contains(name.toString());
+    }
+
+    @Override
+    public boolean containsValue(Object item) {
+        //noinspection SuspiciousMethodCalls
+        return set.contains(item);
+    }
+
+    @Override
+    public void create(String name) {
+        create(name, elementType, Actions.doNothing());
+    }
+
+    @Override
+    public void create(String name, Action<? super T> configAction) {
+        create(name, elementType, configAction);
+    }
+
+    @Override
+    public <S extends T> void create(String name, Class<S> type) {
+        create(name, type, Actions.doNothing());
+    }
+
+    @Override
+    public <S extends T> void create(String name, Class<S> type, Action<? super S> configAction) {
+        S s = instantiator.create(name, type);
+        configAction.execute(s);
+        onCreateAction.execute(s);
+    }
+
+    @Override
+    public void named(String name, Class<? extends RuleSource> ruleSource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void all(Action<? super T> configAction) {
+        set.all(configAction);
+    }
+
+    @Override
+    public void beforeEach(Action<? super T> configAction) {
+        all(configAction);
+    }
+
+    @Override
+    public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void afterEach(Action<? super T> configAction) {
+        all(configAction);
+    }
+
+    @Override
+    public Collection<T> values() {
+        return set;
+    }
+
+    @Override
+    public <S> void beforeEach(Class<S> type, Action<? super S> configAction) {
+        withType(type, configAction);
+    }
+
+    @Override
+    public <S> void afterEach(Class<S> type, Action<? super S> configAction) {
+        withType(type, configAction);
+    }
+
+    @Override
+    public void named(final String name, Action<? super T> configAction) {
+        set.matching(new Spec<T>() {
+            @Override
+            public boolean isSatisfiedBy(T element) {
+                return get(name) == element;
+            }
+        }).all(configAction);
+    }
+
+    @Override
+    public <S> ModelMap<S> withType(final Class<S> type) {
+        if (type.equals(elementType)) {
+            return uncheckedCast(this);
+        }
+
+        if (elementType.isAssignableFrom(type)) {
+            Class<? extends T> castType = uncheckedCast(type);
+            ModelMap<? extends T> subType = toSubtypeMap(castType);
+            return uncheckedCast(subType);
+        }
+
+        return toNonSubtypeMap(type);
+    }
+
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinarySpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinarySpec.java
index e441bd8..286c872 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinarySpec.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinarySpec.java
@@ -23,7 +23,8 @@ import org.gradle.language.base.LanguageSourceSet;
 /**
  * Represents a binary artifact that is the result of building a project component.
  */
- at Incubating @HasInternalProtocol
+ at Incubating
+ at HasInternalProtocol
 public interface BinarySpec extends BuildableModelElement, Named {
     /**
      * Returns a human-consumable display name for this binary.
@@ -36,25 +37,20 @@ public interface BinarySpec extends BuildableModelElement, Named {
     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:
+     * 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>
+     * <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>
      */
     // TODO:DAZ Remove this
     void source(Object source);
 
     /**
+     * The source sets used to compile this binary.
+     */
+    DomainObjectSet<LanguageSourceSet> getSource();
+
+    /**
      * Configures the source sets used to build this binary.
      */
     void sources(Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>> action);
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryTasks.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryTasks.java
index 638b88e..b823c15 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryTasks.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryTasks.java
@@ -30,9 +30,6 @@ import java.lang.annotation.Target;
  * {@link org.gradle.platform.base.BinaryTasks} annotation.
  *
  * <pre autoTested='true'>
- * import org.gradle.model.*
- * import org.gradle.model.collection.*
- *
  * interface SampleComponent extends ComponentSpec {}
  * interface SampleBinary extends BinarySpec {}
  * class DefaultSampleBinary extends BaseBinarySpec implements SampleBinary {}
@@ -52,7 +49,7 @@ import java.lang.annotation.Target;
  *     }
  *
  *     {@literal @}BinaryTasks
- *     void createBinaryTasks(CollectionBuilder<Task> tasks, SampleBinary binary) {
+ *     void createBinaryTasks(ModelMap<Task> tasks, SampleBinary binary) {
  *         tasks.create("${binary.name}Task1", MyCustomBinaryCreationTask)
  *         tasks.create("${binary.name}Task2") {
  *             dependsOn "${binary.name}Task1"
@@ -65,4 +62,4 @@ import java.lang.annotation.Target;
 @Target(ElementType.METHOD)
 @Incubating
 public @interface BinaryTasks {
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryType.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryType.java
index 8c03de5..27bf2d0 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryType.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/BinaryType.java
@@ -31,9 +31,6 @@ import java.lang.annotation.Target;
  * {@link org.gradle.platform.base.BinaryType} annotation.
  *
  * <pre autoTested=''>
- * import org.gradle.model.*
- * import org.gradle.model.collection.*
- *
  * interface SampleBinary extends BinarySpec {}
  * class DefaultSampleBinary extends BaseBinarySpec implements SampleBinary {}
  *
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentBinaries.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentBinaries.java
index 6c92b80..cd23d56 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentBinaries.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentBinaries.java
@@ -31,9 +31,6 @@ import java.lang.annotation.Target;
  * Furthermore the plugin registers 'DefaultSampleBinary' as implementation for {@link org.gradle.platform.base.BinarySpec}.
  *
  * <pre autoTested='true'>
- * import org.gradle.model.*
- * import org.gradle.model.collection.*
- *
  * interface SampleComponent extends ComponentSpec {}
  * interface SampleBinary extends BinarySpec {}
  * class DefaultSampleBinary extends BaseBinarySpec implements SampleBinary {}
@@ -47,7 +44,7 @@ import java.lang.annotation.Target;
  *     }
  *
  *     {@literal @}ComponentBinaries
- *     void createBinariesForSampleLibrary(CollectionBuilder<SampleBinary> binaries, SampleComponent component) {
+ *     void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleComponent component) {
  *         binaries.create("${component.name}Binary", SampleBinary)
  *     }
  * }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpec.java
index 2dac062..fcb4ddc 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpec.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpec.java
@@ -19,6 +19,7 @@ package org.gradle.platform.base;
 import org.gradle.api.*;
 import org.gradle.internal.HasInternalProtocol;
 import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.model.ModelMap;
 
 /**
  * A software component that is built by a Gradle project.
@@ -39,20 +40,20 @@ public interface ComponentSpec extends Named {
     /**
      * The source sets that are used to build this component.
      */
-    DomainObjectSet<LanguageSourceSet> getSource();
+    ModelMap<LanguageSourceSet> getSource();
 
     /**
      * Configures the source sets used to build this component.
      */
-    void sources(Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>> action);
+    void sources(Action<? super ModelMap<LanguageSourceSet>> action);
 
     /**
      * The binaries that are built for this component. You can use this to configure the binaries for this component.
      */
-    DomainObjectSet<BinarySpec> getBinaries();
+    ModelMap<BinarySpec> getBinaries();
 
     /**
      * Configures the binaries that are produced for this component.
      */
-    void binaries(Action<? super DomainObjectSet<BinarySpec>> action);
+    void binaries(Action<? super ModelMap<BinarySpec>> action);
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpecContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpecContainer.java
index 93c8869..6a83888 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpecContainer.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentSpecContainer.java
@@ -16,13 +16,13 @@
 
 package org.gradle.platform.base;
 
-import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
 import org.gradle.api.Incubating;
+import org.gradle.model.ModelMap;
 
 /**
  * A container of software components.
  * TODO:DAZ Merge with org.gradle.api.component.SoftwareComponentContainer
  */
 @Incubating
-public interface ComponentSpecContainer extends ExtensiblePolymorphicDomainObjectContainer<ComponentSpec> {
+public interface ComponentSpecContainer extends ModelMap<ComponentSpec> {
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentType.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentType.java
index 942c92e..ee5c74b 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentType.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/ComponentType.java
@@ -31,9 +31,6 @@ import java.lang.annotation.Target;
  * Furthermore the plugin creates an instance of SampleComponent named 'sampleComponent'.
  *
  * <pre autoTested='true'>
- * import org.gradle.model.*
- * import org.gradle.model.collection.*
- *
  * interface SampleComponent extends ComponentSpec {}
  * class DefaultSampleComponent extends BaseComponentSpec implements SampleComponent {}
  *
@@ -46,7 +43,7 @@ import java.lang.annotation.Target;
  *     }
  *
  *     {@literal @}Mutate
- *     void createSampleLibraryComponents(CollectionBuilder<SampleComponent> componentSpecs) {
+ *     void createSampleLibraryComponents(ModelMap<SampleComponent> componentSpecs) {
  *         componentSpecs.create("sampleComponent")
  *     }
  * }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpec.java
new file mode 100644
index 0000000..0d522ce
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+/**
+ * A dependency onto a Gradle component.
+ */
+ at Incubating
+public interface DependencySpec {
+
+    /**
+     * Returns the project path of the project this dependency refers to.
+     *
+     * @return the project path
+     */
+    @Nullable
+    String getProjectPath();
+
+    /**
+     * Returns the name of the library this dependency refers to. If null, it should be assumed that the project
+     * defines a single library.
+     *
+     * @return the library name
+     */
+    @Nullable
+    String getLibraryName();
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecBuilder.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecBuilder.java
new file mode 100644
index 0000000..d397d7a
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecBuilder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.platform.base;
+
+/**
+ * Interface for a dependency spec builder. Implementations are required to return
+ * immutable dependency specs.
+ */
+public interface DependencySpecBuilder extends DependencySpec {
+    /**
+     * Narrows this dependency specification down to a specific project.
+     *
+     * @param path the project path
+     *
+     * @return this instance
+     */
+    DependencySpecBuilder project(String path);
+
+    /**
+     * Narrows this dependency specification down to a specific library.
+     *
+     * @param name the library name
+     *
+     * @return this instance
+     */
+    DependencySpecBuilder library(String name);
+
+    /**
+     * Builds a concrete immutable {@link DependencySpec} instance.
+     *
+     * @return an immutable dependency specification
+     */
+    DependencySpec build();
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecContainer.java
new file mode 100644
index 0000000..a7ff269
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/DependencySpecContainer.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base;
+
+import org.gradle.api.Incubating;
+
+import java.util.Set;
+
+/**
+ * A container for dependency specifications.
+ */
+ at Incubating
+public interface DependencySpecContainer extends Set<DependencySpec> {
+    /**
+     * Defines a new dependency, based on a project path. The returned dependency can be mutated.
+     *
+     * @param path the project path
+     *
+     * @return a mutable dependency, added to this container
+     */
+    DependencySpec project(String path);
+
+    /**
+     * Defines a new dependency, based on a library name. The returned dependency can be mutated.
+     *
+     * @param name of the library
+     *
+     * @return a mutable dependency, added to this container
+     */
+    DependencySpec library(String name);
+
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/LanguageType.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/LanguageType.java
index fa96c2c..ea3ada2 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/LanguageType.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/LanguageType.java
@@ -28,9 +28,6 @@ import java.lang.annotation.Target;
  * {@link LanguageType} annotation.
  *
  * <pre autoTested=''>
- * import org.gradle.model.*
- * import org.gradle.model.collection.*
- *
  * interface CustomLanguageSourceSet extends LanguageSourceSet {}
  * class DefaultCustomLanguageSourceSet extends BaseLanguageSourceSet implements CustomLanguageSourceSet {}
  *
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/binary/BaseBinarySpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/binary/BaseBinarySpec.java
index e36ed46..59de47b 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/binary/BaseBinarySpec.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/binary/BaseBinarySpec.java
@@ -31,16 +31,13 @@ import org.gradle.platform.base.BinaryTasksCollection;
 import org.gradle.platform.base.ModelInstantiationException;
 import org.gradle.platform.base.internal.BinaryBuildAbility;
 import org.gradle.platform.base.internal.BinarySpecInternal;
-import org.gradle.platform.base.internal.FixedBuildAbility;
 import org.gradle.platform.base.internal.DefaultBinaryTasksCollection;
+import org.gradle.platform.base.internal.FixedBuildAbility;
 
 /**
- * Base class for custom binary implementations.
- * A custom implementation of {@link org.gradle.platform.base.BinarySpec} must extend this type.
- *
- * TODO at the moment leaking BinarySpecInternal here to generate lifecycleTask in
- * LanguageBasePlugin$createLifecycleTaskForBinary#createLifecycleTaskForBinary rule
+ * Base class for custom binary implementations. A custom implementation of {@link org.gradle.platform.base.BinarySpec} must extend this type.
  *
+ * TODO at the moment leaking BinarySpecInternal here to generate lifecycleTask in LanguageBasePlugin$createLifecycleTaskForBinary#createLifecycleTaskForBinary rule
  */
 @Incubating
 public abstract class BaseBinarySpec extends AbstractBuildableModelElement implements BinarySpecInternal {
@@ -112,10 +109,12 @@ public abstract class BaseBinarySpec extends AbstractBuildableModelElement imple
         sourceSets.setMainSources(sources);
     }
 
+    @Override
     public DomainObjectSet<LanguageSourceSet> getSource() {
         return sourceSets.getSources();
     }
 
+
     public void sources(Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>> action) {
         action.execute(sourceSets.getMainSources());
     }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/component/BaseComponentSpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/component/BaseComponentSpec.java
index aa488b3..7959c52 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/component/BaseComponentSpec.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/component/BaseComponentSpec.java
@@ -17,38 +17,89 @@
 package org.gradle.platform.base.component;
 
 import org.gradle.api.Action;
-import org.gradle.api.DomainObjectSet;
 import org.gradle.api.Incubating;
-import org.gradle.api.PolymorphicDomainObjectContainer;
-import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.Named;
+import org.gradle.api.Transformer;
+import org.gradle.internal.Actions;
+import org.gradle.internal.Cast;
+import org.gradle.internal.TriAction;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.language.base.FunctionalSourceSet;
 import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.LanguageSourceSetInternal;
+import org.gradle.model.ModelMap;
+import org.gradle.model.collection.internal.BridgedCollections;
+import org.gradle.model.collection.internal.ModelMapModelProjection;
+import org.gradle.model.collection.internal.PolymorphicModelMapProjection;
+import org.gradle.model.internal.core.*;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.NestedModelRuleDescriptor;
+import org.gradle.model.internal.registry.RuleContext;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.model.internal.type.ModelTypes;
 import org.gradle.platform.base.*;
+import org.gradle.platform.base.internal.BinarySpecFactory;
+import org.gradle.platform.base.internal.BinarySpecInternal;
 import org.gradle.platform.base.internal.ComponentSpecInternal;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
- * Base class for custom component implementations.
- * A custom implementation of {@link ComponentSpec} must extend this type.
+ * Base class for custom component implementations. A custom implementation of {@link ComponentSpec} must extend this type.
  */
 @Incubating
 public abstract class BaseComponentSpec implements ComponentSpecInternal {
+
+    private static final TriAction<MutableModelNode, BinarySpec, List<ModelView<?>>> CREATE_BINARY_SOURCE_SET = new TriAction<MutableModelNode, BinarySpec, List<ModelView<?>>>() {
+        @Override
+        public void execute(MutableModelNode modelNode, BinarySpec binarySpec, List<ModelView<?>> views) {
+            FunctionalSourceSet componentSources = ModelViews.getInstance(views.get(0), FunctionalSourceSet.class);
+            BinarySpecInternal binarySpecInternal = Cast.uncheckedCast(binarySpec);
+            FunctionalSourceSet binarySources = componentSources.copy(binarySpec.getName());
+            binarySpecInternal.setBinarySources(binarySources);
+        }
+    };
+
+    private static final Transformer<FunctionalSourceSet, MutableModelNode> PUSH_FUNCTIONAL_SOURCE_SET_TO_NODE = new Transformer<FunctionalSourceSet, MutableModelNode>() {
+        @Override
+        public FunctionalSourceSet transform(MutableModelNode modelNode) {
+            BaseComponentSpec componentSpec = (BaseComponentSpec) modelNode.getParent().getPrivateData(ModelType.of(ComponentSpec.class));
+            return componentSpec.mainSourceSet;
+        }
+    };
+
+    private static final Transformer<NamedEntityInstantiator<LanguageSourceSet>, MutableModelNode> SOURCE_SET_CREATOR = new Transformer<NamedEntityInstantiator<LanguageSourceSet>, MutableModelNode>() {
+        @Override
+        public NamedEntityInstantiator<LanguageSourceSet> transform(final MutableModelNode modelNode) {
+            return new NamedEntityInstantiator<LanguageSourceSet>() {
+                @Override
+                public <S extends LanguageSourceSet> S create(String name, Class<S> type) {
+                    FunctionalSourceSet sourceSet = modelNode.getPrivateData(FunctionalSourceSet.class);
+                    S s = sourceSet.getEntityInstantiator().create(name, type);
+                    sourceSet.add(s);
+                    return s;
+                }
+            };
+        }
+    };
+
     private static ThreadLocal<ComponentInfo> nextComponentInfo = new ThreadLocal<ComponentInfo>();
     private final FunctionalSourceSet mainSourceSet;
-
     private final ComponentSpecIdentifier identifier;
     private final String typeName;
-    private final DomainObjectSet<BinarySpec> binaries = new DefaultDomainObjectSet<BinarySpec>(BinarySpec.class);
 
-    public static <T extends BaseComponentSpec> T create(Class<T> type, ComponentSpecIdentifier identifier, FunctionalSourceSet mainSourceSet, Instantiator instantiator) {
+    private final MutableModelNode binaries;
+    private final MutableModelNode sources;
+    private MutableModelNode modelNode;
+
+    public static <T extends BaseComponentSpec> T create(Class<T> type, ComponentSpecIdentifier identifier, MutableModelNode modelNode, FunctionalSourceSet mainSourceSet, Instantiator instantiator) {
         if (type.equals(BaseComponentSpec.class)) {
             throw new ModelInstantiationException("Cannot create instance of abstract class BaseComponentSpec.");
         }
-        nextComponentInfo.set(new ComponentInfo(identifier, type.getSimpleName(), mainSourceSet));
+        nextComponentInfo.set(new ComponentInfo(identifier, modelNode, type.getSimpleName(), mainSourceSet, instantiator));
         try {
             try {
                 return instantiator.newInstance(type);
@@ -72,6 +123,53 @@ public abstract class BaseComponentSpec implements ComponentSpecInternal {
         this.identifier = info.componentIdentifier;
         this.typeName = info.typeName;
         this.mainSourceSet = info.sourceSets;
+
+        modelNode = info.modelNode;
+        modelNode.addLink(
+            ModelCreators.of(
+                modelNode.getPath().child("binaries"), Actions.doNothing())
+                .descriptor(modelNode.getDescriptor(), ".binaries")
+                .withProjection(
+                    ModelMapModelProjection.unmanaged(
+                        BinarySpec.class,
+                        NodeBackedModelMap.createUsingFactory(ModelReference.of(BinarySpecFactory.class))
+                    )
+                )
+                .build()
+        );
+        binaries = modelNode.getLink("binaries");
+        assert binaries != null;
+
+        final ModelPath sourcesNodePath = modelNode.getPath().child("sources");
+        binaries.applyToAllLinks(ModelActionRole.Defaults, DirectNodeInputUsingModelAction.of(
+            ModelReference.of(BinarySpecInternal.PUBLIC_MODEL_TYPE),
+            new NestedModelRuleDescriptor(modelNode.getDescriptor(), ".sources"),
+            Collections.<ModelReference<?>>singletonList(ModelReference.of(sourcesNodePath, FunctionalSourceSet.class)),
+            CREATE_BINARY_SOURCE_SET
+        ));
+
+        ModelRuleDescriptor sourcesDescriptor = new NestedModelRuleDescriptor(modelNode.getDescriptor(), ".sources");
+        modelNode.addLink(
+            BridgedCollections
+                .creator(
+                    ModelReference.of(sourcesNodePath, FunctionalSourceSet.class),
+                    PUSH_FUNCTIONAL_SOURCE_SET_TO_NODE,
+                    new Named.Namer(),
+                    sourcesDescriptor.toString(),
+                    BridgedCollections.itemDescriptor(sourcesDescriptor.toString())
+                )
+                .withProjection(
+                    PolymorphicModelMapProjection.ofEager(
+                        LanguageSourceSetInternal.PUBLIC_MODEL_TYPE,
+                        NodeBackedModelMap.createUsingParentNode(SOURCE_SET_CREATOR)
+                    )
+                )
+                .withProjection(UnmanagedModelProjection.of(FunctionalSourceSet.class))
+                .build()
+        );
+
+        this.sources = modelNode.getLink("sources");
+        assert this.sources != null;
     }
 
     public String getName() {
@@ -95,25 +193,38 @@ public abstract class BaseComponentSpec implements ComponentSpecInternal {
         return getDisplayName();
     }
 
-    public DomainObjectSet<LanguageSourceSet> getSource() {
-        return new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class, mainSourceSet);
+    @Override
+    public ModelMap<LanguageSourceSet> getSource() {
+        sources.ensureUsable();
+        return sources.asWritable(
+            ModelTypes.modelMap(LanguageSourceSet.class),
+            RuleContext.nest(modelNode.toString() + ".getSources()"),
+            Collections.<ModelView<?>>emptyList()
+        ).getInstance();
     }
 
-    public DomainObjectSet<BinarySpec> getBinaries() {
-        return binaries;
+    @Override
+    public void sources(Action<? super ModelMap<LanguageSourceSet>> action) {
+        action.execute(getSource());
     }
 
     @Override
-    public void binaries(Action<? super DomainObjectSet<BinarySpec>> action) {
-        action.execute(binaries);
+    public ModelMap<BinarySpec> getBinaries() {
+        binaries.ensureUsable();
+        return binaries.asWritable(
+            ModelTypes.modelMap(BinarySpecInternal.PUBLIC_MODEL_TYPE),
+            RuleContext.nest(identifier.toString() + ".getBinaries()"),
+            Collections.<ModelView<?>>emptyList()
+        ).getInstance();
     }
 
-    public FunctionalSourceSet getSources() {
-        return mainSourceSet;
+    @Override
+    public void binaries(Action<? super ModelMap<BinarySpec>> action) {
+        action.execute(getBinaries());
     }
 
-    public void sources(Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>> action) {
-        action.execute(mainSourceSet);
+    public FunctionalSourceSet getSources() {
+        return mainSourceSet;
     }
 
     public Set<Class<? extends TransformationFileType>> getInputTypes() {
@@ -122,15 +233,24 @@ public abstract class BaseComponentSpec implements ComponentSpecInternal {
 
     private static class ComponentInfo {
         final ComponentSpecIdentifier componentIdentifier;
+        private final MutableModelNode modelNode;
         final String typeName;
         final FunctionalSourceSet sourceSets;
+        final Instantiator instantiator;
 
-        private ComponentInfo(ComponentSpecIdentifier componentIdentifier,
-                              String typeName,
-                              FunctionalSourceSet sourceSets) {
+        private ComponentInfo(
+            ComponentSpecIdentifier componentIdentifier,
+            MutableModelNode modelNode,
+            String typeName,
+            FunctionalSourceSet sourceSets,
+            Instantiator instantiator
+        ) {
             this.componentIdentifier = componentIdentifier;
+            this.modelNode = modelNode;
             this.typeName = typeName;
             this.sourceSets = sourceSets;
+            this.instantiator = instantiator;
         }
     }
-}
\ No newline at end of file
+
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecFactory.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecFactory.java
new file mode 100644
index 0000000..f399d2b
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base.internal;
+
+import org.gradle.model.internal.core.BaseInstanceFactory;
+import org.gradle.platform.base.BinarySpec;
+
+public class BinarySpecFactory extends BaseInstanceFactory<BinarySpec, String> {
+    public BinarySpecFactory(String displayName) {
+        super(displayName);
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecInternal.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecInternal.java
index 908ff49..ee80a85 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecInternal.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/BinarySpecInternal.java
@@ -17,9 +17,13 @@
 package org.gradle.platform.base.internal;
 
 import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.model.internal.type.ModelType;
 import org.gradle.platform.base.BinarySpec;
 
 public interface BinarySpecInternal extends BinarySpec {
+
+    ModelType<BinarySpec> PUBLIC_MODEL_TYPE = ModelType.of(BinarySpec.class);
+
     FunctionalSourceSet getBinarySources();
 
     void setBinarySources(FunctionalSourceSet sources);
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecAware.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecAware.java
new file mode 100644
index 0000000..c4d06f8
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecAware.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base.internal;
+
+import org.gradle.platform.base.ComponentSpec;
+
+public interface ComponentSpecAware {
+
+    void setComponent(ComponentSpec componentSpec);
+
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecFactory.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecFactory.java
new file mode 100644
index 0000000..5e4ff2f
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base.internal;
+
+import org.gradle.model.internal.core.BaseInstanceFactory;
+import org.gradle.platform.base.ComponentSpec;
+
+public class ComponentSpecFactory extends BaseInstanceFactory<ComponentSpec, String> {
+    public ComponentSpecFactory(String displayName) {
+        super(displayName);
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecInternal.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecInternal.java
index ea282ee..6e17bbd 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecInternal.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/ComponentSpecInternal.java
@@ -27,4 +27,5 @@ public interface ComponentSpecInternal extends ComponentSpec {
     FunctionalSourceSet getSources();
 
     Set<Class<? extends TransformationFileType>> getInputTypes();
+
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultBinaryContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultBinaryContainer.java
index ffe65c8..996cee0 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultBinaryContainer.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultBinaryContainer.java
@@ -16,11 +16,12 @@
 package org.gradle.platform.base.internal;
 
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.model.internal.core.NamedEntityInstantiator;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.platform.base.BinarySpec;
-import org.gradle.platform.base.internal.rules.RuleAwarePolymorphicDomainObjectContainer;
+import org.gradle.api.internal.rules.AddOnlyRuleAwarePolymorphicDomainObjectContainer;
 
-public class DefaultBinaryContainer extends RuleAwarePolymorphicDomainObjectContainer<BinarySpec> implements BinaryContainer {
+public class DefaultBinaryContainer extends AddOnlyRuleAwarePolymorphicDomainObjectContainer<BinarySpec> implements BinaryContainer, NamedEntityInstantiator<BinarySpec> {
     public DefaultBinaryContainer(Instantiator instantiator) {
         super(BinarySpec.class, instantiator);
     }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultComponentSpecContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultComponentSpecContainer.java
deleted file mode 100644
index bd31962..0000000
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultComponentSpecContainer.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.platform.base.internal;
-
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.platform.base.ComponentSpec;
-import org.gradle.platform.base.ComponentSpecContainer;
-import org.gradle.platform.base.internal.rules.RuleAwarePolymorphicDomainObjectContainer;
-
-public class DefaultComponentSpecContainer extends RuleAwarePolymorphicDomainObjectContainer<ComponentSpec> implements ComponentSpecContainer {
-
-    public DefaultComponentSpecContainer(Instantiator instantiator) {
-        super(ComponentSpec.class, instantiator);
-    }
-}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpec.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpec.java
new file mode 100644
index 0000000..fbccc16
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpec.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.platform.base.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.platform.base.DependencySpec;
+import org.gradle.platform.base.DependencySpecBuilder;
+
+public class DefaultDependencySpec implements DependencySpec {
+    private final String projectPath;
+    private final String libraryName;
+
+    public DefaultDependencySpec(String libraryName, String projectPath) {
+        if (libraryName==null && projectPath==null) {
+            throw new IllegalArgumentException("A dependency spec must have at least one of project or library name not null");
+        }
+        this.libraryName = libraryName;
+        this.projectPath = projectPath;
+    }
+
+    @Override
+    public String getProjectPath() {
+        return projectPath;
+    }
+
+    @Nullable
+    @Override
+    public String getLibraryName() {
+        return libraryName;
+    }
+
+    public static class Builder implements DependencySpecBuilder {
+        private String projectPath;
+        private String libraryName;
+
+        @Override
+        public DependencySpecBuilder project(String path) {
+            projectPath = path;
+            return this;
+        }
+
+        @Override
+        public DependencySpecBuilder library(String name) {
+            libraryName = name;
+            return this;
+        }
+
+        @Override
+        public DependencySpec build() {
+            return new DefaultDependencySpec(libraryName, projectPath);
+        }
+
+        @Override
+        @Nullable
+        public String getProjectPath() {
+            return projectPath;
+        }
+
+        @Nullable
+        @Override
+        public String getLibraryName() {
+            return libraryName;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultDependencySpec that = (DefaultDependencySpec) o;
+
+        if (projectPath != null ? !projectPath.equals(that.projectPath) : that.projectPath != null) {
+            return false;
+        }
+        return !(libraryName != null ? !libraryName.equals(that.libraryName) : that.libraryName != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = projectPath != null ? projectPath.hashCode() : 0;
+        result = 31 * result + (libraryName != null ? libraryName.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpecContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpecContainer.java
new file mode 100644
index 0000000..55f8f19
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/DefaultDependencySpecContainer.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base.internal;
+
+import com.google.common.collect.ImmutableSet;
+import org.gradle.api.Action;
+import org.gradle.platform.base.DependencySpec;
+import org.gradle.platform.base.DependencySpecBuilder;
+import org.gradle.platform.base.DependencySpecContainer;
+
+import java.util.*;
+
+public class DefaultDependencySpecContainer implements DependencySpecContainer {
+
+    private final List<DefaultDependencySpec.Builder> builders = new LinkedList<DefaultDependencySpec.Builder>();
+
+    @Override
+    public DependencySpecBuilder project(final String path) {
+        return doCreate(new Action<DefaultDependencySpec.Builder>() {
+            @Override
+            public void execute(DefaultDependencySpec.Builder builder) {
+                builder.project(path);
+            }
+        });
+    }
+
+    @Override
+    public DependencySpecBuilder library(final String name) {
+        return doCreate(new Action<DefaultDependencySpec.Builder>() {
+            @Override
+            public void execute(DefaultDependencySpec.Builder builder) {
+                builder.library(name);
+            }
+        });
+    }
+
+    private Collection<DependencySpec> getDependencies() {
+        if (builders.isEmpty()) {
+            return Collections.emptySet();
+        }
+        ArrayList<DependencySpec> specs = new ArrayList<DependencySpec>(builders.size());
+        for (DefaultDependencySpec.Builder builder : builders) {
+            specs.add(builder.build());
+        }
+        return ImmutableSet.copyOf(specs);
+    }
+
+    private DefaultDependencySpec.Builder doCreate(Action<? super DefaultDependencySpec.Builder> action) {
+        DefaultDependencySpec.Builder builder = new DefaultDependencySpec.Builder();
+        action.execute(builder);
+        builders.add(builder);
+        return builder;
+    }
+
+    @Override
+    public boolean add(DependencySpec dependencySpec) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int size() {
+        return getDependencies().size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return builders.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<DependencySpec> iterator() {
+        return getDependencies().iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return getDependencies().toArray();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T[] toArray(T[] a) {
+        return (T[]) getDependencies().toArray((DefaultDependencySpec[])a);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends DependencySpec> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/AbstractAnnotationDrivenComponentModelRuleExtractor.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/AbstractAnnotationDrivenComponentModelRuleExtractor.java
index 3545476..1f4a28b 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/AbstractAnnotationDrivenComponentModelRuleExtractor.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/AbstractAnnotationDrivenComponentModelRuleExtractor.java
@@ -16,7 +16,7 @@
 
 package org.gradle.platform.base.internal.registry;
 
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.ModelMap;
 import org.gradle.model.internal.core.ModelReference;
 import org.gradle.model.internal.inspect.AbstractAnnotationDrivenModelRuleExtractor;
 import org.gradle.model.internal.inspect.MethodRuleDefinition;
@@ -35,18 +35,20 @@ public abstract class AbstractAnnotationDrivenComponentModelRuleExtractor<T exte
         }
     }
 
-    protected <V> void visitCollectionBuilderSubject(RuleMethodDataCollector dataCollector, MethodRuleDefinition<?, ?> ruleDefinition, Class<V> typeParameter) {
+    protected <V> void visitSubject(RuleMethodDataCollector dataCollector, MethodRuleDefinition<?, ?> ruleDefinition, Class<V> typeParameter) {
         if (ruleDefinition.getReferences().size() == 0) {
-            throw new InvalidModelException(String.format("Method %s must have a parameter of type '%s'.", getDescription(), CollectionBuilder.class.getName()));
+            throw new InvalidModelException(String.format("Method %s must have a parameter of type '%s'.", getDescription(), ModelMap.class.getName()));
         }
 
         @SuppressWarnings("ConstantConditions") ModelType<?> builder = ruleDefinition.getSubjectReference().getType();
 
-        if (!ModelType.of(CollectionBuilder.class).isAssignableFrom(builder)) {
-            throw new InvalidModelException(String.format("Method %s first parameter must be of type '%s'.", getDescription(), CollectionBuilder.class.getName()));
+        @SuppressWarnings("deprecation")
+        Class<?> containerClass = org.gradle.model.collection.CollectionBuilder.class;
+        if (!ModelType.of(containerClass).isAssignableFrom(builder)) {
+            throw new InvalidModelException(String.format("Method %s first parameter must be of type '%s'.", getDescription(), ModelMap.class.getName()));
         }
         if (builder.getTypeVariables().size() != 1) {
-            throw new InvalidModelException(String.format("Parameter of type '%s' must declare a type parameter extending '%s'.", CollectionBuilder.class.getSimpleName(), typeParameter.getSimpleName()));
+            throw new InvalidModelException(String.format("Parameter of type '%s' must declare a type parameter extending '%s'.", ModelMap.class.getSimpleName(), typeParameter.getSimpleName()));
         }
         ModelType<?> subType = builder.getTypeVariables().get(0);
 
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractor.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractor.java
index d4413ec..b6dfe01 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractor.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractor.java
@@ -19,17 +19,15 @@ package org.gradle.platform.base.internal.registry;
 import com.google.common.collect.ImmutableList;
 import org.gradle.api.Action;
 import org.gradle.api.Task;
-import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.internal.Cast;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.model.InvalidModelRuleDeclarationException;
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.ModelMap;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
 import org.gradle.model.internal.inspect.MethodRuleDefinition;
 import org.gradle.model.internal.type.ModelType;
-import org.gradle.model.internal.type.ModelTypes;
 import org.gradle.platform.base.BinarySpec;
 import org.gradle.platform.base.BinaryTasks;
 import org.gradle.platform.base.InvalidModelException;
@@ -49,14 +47,21 @@ public class BinaryTasksModelRuleExtractor extends AbstractAnnotationDrivenCompo
             verifyMethodSignature(dataCollector, ruleDefinition);
 
             final Class<S> binaryType = dataCollector.getParameterType(BinarySpec.class);
-
             final BinaryTaskRule<R, S> binaryTaskRule = new BinaryTaskRule<R, S>(binaryType, ruleDefinition);
-            return new ExtractedModelAction(ModelActionRole.Defaults, ImmutableList.of(ComponentModelBasePlugin.class), DirectNodeModelAction.of(ModelReference.of("binaries"), new SimpleModelRuleDescriptor("binaries*.create()"), new Action<MutableModelNode>() {
-                @Override
-                public void execute(MutableModelNode modelNode) {
-                    modelNode.applyToAllLinks(ModelActionRole.Finalize, binaryTaskRule);
-                }
-            }));
+            return new ExtractedModelAction(
+                ModelActionRole.Defaults,
+                ImmutableList.of(ComponentModelBasePlugin.class),
+                DirectNodeNoInputsModelAction.of(
+                    ModelReference.of("binaries"),
+                    new SimpleModelRuleDescriptor("binaries*.create()"),
+                    new Action<MutableModelNode>() {
+                        @Override
+                        public void execute(MutableModelNode modelNode) {
+                            modelNode.applyToAllLinks(ModelActionRole.Finalize, binaryTaskRule);
+                        }
+                    }
+                )
+            );
         } catch (InvalidModelException e) {
             throw invalidModelRule(ruleDefinition, e);
         }
@@ -64,7 +69,7 @@ public class BinaryTasksModelRuleExtractor extends AbstractAnnotationDrivenCompo
 
     private void verifyMethodSignature(RuleMethodDataCollector taskDataCollector, MethodRuleDefinition<?, ?> ruleDefinition) {
         assertIsVoidMethod(ruleDefinition);
-        visitCollectionBuilderSubject(taskDataCollector, ruleDefinition, Task.class);
+        visitSubject(taskDataCollector, ruleDefinition, Task.class);
         visitDependency(taskDataCollector, ruleDefinition, ModelType.of(BinarySpec.class));
     }
 
@@ -76,35 +81,35 @@ public class BinaryTasksModelRuleExtractor extends AbstractAnnotationDrivenCompo
         return new InvalidModelRuleDeclarationException(sb.toString(), e);
     }
 
-    private class BinaryTaskRule<R, T extends BinarySpec> extends CollectionBuilderBasedRule<R, Task, T, T> {
+    private class BinaryTaskRule<R, T extends BinarySpec> extends ModelMapBasedRule<R, Task, T, T> {
 
         public BinaryTaskRule(Class<T> binaryType, MethodRuleDefinition<R, ?> ruleDefinition) {
             super(ModelReference.of(binaryType), binaryType, ruleDefinition);
         }
 
-        public void execute(MutableModelNode modelNode, final T binary, List<ModelView<?>> inputs) {
-            DefaultCollectionBuilder<TaskInternal> collectionBuilder = new DefaultCollectionBuilder<TaskInternal>(
-                    ModelType.of(TaskInternal.class),
-                    getDescriptor(),
-                    modelNode,
-                    DefaultCollectionBuilder.createAndStoreVia(
-                            ModelReference.of(ITaskFactory.class),
-                            ModelReference.of(modelNode.getPath().child("__tasks"), ModelTypes.collectionOf(Task .class))
-                    )
-
-            ) {
-                @Override
-                protected <S extends TaskInternal> void onCreate(final String name, ModelType<S> type) {
-                    Task task = get(name);
-                    binary.builtBy(task);
-                }
-            };
-
-            CollectionBuilder<Task> cast = Cast.uncheckedCast(collectionBuilder);
-
-            List<ModelView<?>> inputsWithBinary = new ArrayList<ModelView<?>>(inputs.size() + 1);
-            inputsWithBinary.addAll(inputs);
-            inputsWithBinary.add(new InstanceModelView<T>(getSubject().getPath(), getSubject().getType(), binary));
+        @Override
+        public List<ModelReference<?>> getInputs() {
+            return ImmutableList.<ModelReference<?>>builder().add(ModelReference.of(ITaskFactory.class)).addAll(super.getInputs()).build();
+        }
+
+        public void execute(final MutableModelNode modelNode, final T binary, List<ModelView<?>> inputs) {
+            NamedEntityInstantiator<Task> taskFactory = Cast.uncheckedCast(ModelViews.getInstance(inputs.get(0), ITaskFactory.class));
+            ModelMap<Task> cast = DomainObjectSetBackedModelMap.wrap(
+                Task.class,
+                binary.getTasks(),
+                taskFactory,
+                new Task.Namer(),
+                new Action<Task>() {
+                    @Override
+                    public void execute(Task task) {
+                        binary.getTasks().add(task);
+                        binary.builtBy(task);
+                    }
+                });
+
+            List<ModelView<?>> inputsWithBinary = new ArrayList<ModelView<?>>(inputs.size());
+            inputsWithBinary.addAll(inputs.subList(1, inputs.size()));
+            inputsWithBinary.add(InstanceModelView.of(getSubject().getPath(), getSubject().getType(), binary));
 
             invoke(inputsWithBinary, cast, binary, binary);
         }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractor.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractor.java
index bc2f300..ead55bf 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractor.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractor.java
@@ -22,6 +22,8 @@ import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.base.internal.model.BinarySpecFactoryRegistry;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.model.internal.core.*;
 import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
@@ -31,18 +33,16 @@ import org.gradle.platform.base.BinarySpec;
 import org.gradle.platform.base.BinaryType;
 import org.gradle.platform.base.BinaryTypeBuilder;
 import org.gradle.platform.base.binary.BaseBinarySpec;
-import org.gradle.platform.base.internal.DefaultBinaryContainer;
 import org.gradle.platform.base.internal.builder.TypeBuilderInternal;
 
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.List;
 
-public class BinaryTypeModelRuleExtractor extends TypeModelRuleExtractor<BinaryType, BinarySpec, BaseBinarySpec> {
-    private final Instantiator instantiator;
+import static org.gradle.internal.Cast.uncheckedCast;
 
-    public BinaryTypeModelRuleExtractor(final Instantiator instantiator) {
+public class BinaryTypeModelRuleExtractor extends TypeModelRuleExtractor<BinaryType, BinarySpec, BaseBinarySpec> {
+    public BinaryTypeModelRuleExtractor() {
         super("binary", BinarySpec.class, BaseBinarySpec.class, BinaryTypeBuilder.class, JavaReflectionUtil.factory(DirectInstantiator.INSTANCE, DefaultBinaryTypeBuilder.class));
-        this.instantiator = instantiator;
     }
 
     @Override
@@ -50,7 +50,7 @@ public class BinaryTypeModelRuleExtractor extends TypeModelRuleExtractor<BinaryT
         ImmutableList<Class<?>> dependencies = ImmutableList.<Class<?>>of(ComponentModelBasePlugin.class);
         ModelType<? extends BaseBinarySpec> implementation = determineImplementationType(type, builder);
         if (implementation != null) {
-            ModelAction<?> mutator = new RegistrationAction(type, implementation, ruleDefinition.getDescriptor(), instantiator);
+            ModelAction<?> mutator = new RegistrationAction(type, implementation, ruleDefinition.getDescriptor());
             return new ExtractedModelAction(ModelActionRole.Defaults, dependencies, mutator);
         }
         return new DependencyOnlyExtractedModelRule(dependencies);
@@ -62,25 +62,23 @@ public class BinaryTypeModelRuleExtractor extends TypeModelRuleExtractor<BinaryT
         }
     }
 
-    private static class RegistrationAction implements ModelAction<DefaultBinaryContainer> {
+    private static class RegistrationAction implements ModelAction<BinarySpecFactoryRegistry> {
         private final ModelType<? extends BinarySpec> publicType;
         private final ModelType<? extends BaseBinarySpec> implementationType;
         private final ModelRuleDescriptor descriptor;
-        private final Instantiator instantiator;
-        private final ModelReference<DefaultBinaryContainer> subject;
+        private final ModelReference<BinarySpecFactoryRegistry> subject;
         private final List<ModelReference<?>> inputs;
 
-        public RegistrationAction(ModelType<? extends BinarySpec> publicType, ModelType<? extends BaseBinarySpec> implementationType, ModelRuleDescriptor descriptor, Instantiator instantiator) {
+        public RegistrationAction(ModelType<? extends BinarySpec> publicType, ModelType<? extends BaseBinarySpec> implementationType, ModelRuleDescriptor descriptor) {
             this.publicType = publicType;
             this.implementationType = implementationType;
             this.descriptor = descriptor;
-            this.instantiator = instantiator;
-            this.subject = ModelReference.of(DefaultBinaryContainer.class);
-            this.inputs = Collections.<ModelReference<?>>singletonList(ModelReference.of(ITaskFactory.class));
+            this.subject = ModelReference.of(BinarySpecFactoryRegistry.class);
+            this.inputs = Arrays.<ModelReference<?>>asList(ModelReference.of(ServiceRegistry.class), ModelReference.of(ITaskFactory.class));
         }
 
         @Override
-        public ModelReference<DefaultBinaryContainer> getSubject() {
+        public ModelReference<BinarySpecFactoryRegistry> getSubject() {
             return subject;
         }
 
@@ -95,14 +93,17 @@ public class BinaryTypeModelRuleExtractor extends TypeModelRuleExtractor<BinaryT
         }
 
         @Override
-        public void execute(MutableModelNode modelNode, DefaultBinaryContainer binaries, final List<ModelView<?>> inputs) {
-            @SuppressWarnings("unchecked")
-            Class<BinarySpec> publicClass = (Class<BinarySpec>) publicType.getConcreteClass();
-            binaries.registerFactory(publicClass, new NamedDomainObjectFactory<BaseBinarySpec>() {
+        public void execute(MutableModelNode modelNode, BinarySpecFactoryRegistry factories, final List<ModelView<?>> inputs) {
+            final Class<BinarySpec> publicClass = uncheckedCast(publicType.getConcreteClass());
+            ServiceRegistry serviceRegistry = ModelViews.assertType(inputs.get(0), ModelType.of(ServiceRegistry.class)).getInstance();
+            final Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+            final ModelView<ITaskFactory> taskFactory = ModelViews.assertType(inputs.get(1), ModelType.of(ITaskFactory.class));
+            NamedDomainObjectFactory<BaseBinarySpec> factory = new NamedDomainObjectFactory<BaseBinarySpec>() {
                 public BaseBinarySpec create(String name) {
-                    return BaseBinarySpec.create(implementationType.getConcreteClass(), name, instantiator, ModelViews.assertType(inputs.get(0), ModelType.of(ITaskFactory.class)).getInstance());
+                    return BaseBinarySpec.create(implementationType.getConcreteClass(), name, instantiator, taskFactory.getInstance());
                 }
-            }, descriptor);
+            };
+            factories.registerFactory(publicClass, factory, descriptor);
         }
     }
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/CollectionBuilderBasedRule.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/CollectionBuilderBasedRule.java
deleted file mode 100644
index 4cd2058..0000000
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/CollectionBuilderBasedRule.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.platform.base.internal.registry;
-
-import com.google.common.collect.ImmutableList;
-import org.gradle.api.specs.Spec;
-import org.gradle.model.collection.CollectionBuilder;
-import org.gradle.model.internal.core.ModelAction;
-import org.gradle.model.internal.core.ModelReference;
-import org.gradle.model.internal.core.ModelView;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-import org.gradle.model.internal.inspect.MethodRuleDefinition;
-import org.gradle.model.internal.type.ModelType;
-import org.gradle.util.CollectionUtils;
-
-import java.util.Arrays;
-import java.util.List;
-
-public abstract class CollectionBuilderBasedRule<R, S, T, C> implements ModelAction<C> {
-    private final ModelReference<C> subject;
-    private final Class<? extends T> baseType;
-    private final MethodRuleDefinition<R, ?> ruleDefinition;
-
-    private ImmutableList<ModelReference<?>> inputs;
-    protected int baseTypeParameterIndex;
-
-    public CollectionBuilderBasedRule(ModelReference<C> subject, Class<? extends T> baseType, MethodRuleDefinition<R, ?> ruleDefinition, ModelReference<?>... additionalInput) {
-        this.subject = subject;
-        this.baseType = baseType;
-        this.ruleDefinition = ruleDefinition;
-        this.inputs = calculateInputs(Arrays.asList(additionalInput));
-    }
-
-    public List<ModelReference<?>> getInputs() {
-        return this.inputs;
-    }
-
-    public ModelReference<C> getSubject() {
-        return subject;
-    }
-
-    public ModelRuleDescriptor getDescriptor() {
-        return ruleDefinition.getDescriptor();
-    }
-
-    private ImmutableList<ModelReference<?>> calculateInputs(List<ModelReference<?>> modelReferences) {
-        final List<ModelReference<?>> references = this.ruleDefinition.getReferences().subList(1, this.ruleDefinition.getReferences().size());
-        final List<ModelReference<?>> filteredReferences = CollectionUtils.filter(references, new Spec<ModelReference<?>>() {
-            public boolean isSatisfiedBy(ModelReference<?> element) {
-                if (element.getType().equals(ModelType.of(baseType))) {
-                    baseTypeParameterIndex = references.indexOf(element) + 1;
-                    return false;
-                }
-                return true;
-            }
-        });
-
-        ImmutableList.Builder<ModelReference<?>> allInputs = ImmutableList.builder();
-        allInputs.addAll(modelReferences);
-        allInputs.addAll(filteredReferences);
-        return allInputs.build();
-    }
-
-    protected void invoke(List<ModelView<?>> inputs, CollectionBuilder<S> collectionBuilder, T baseTypeParameter, Object ignoredInput) {
-        Object[] args = new Object[inputs.size() + 1];
-        args[0] = collectionBuilder;
-        args[baseTypeParameterIndex] = baseTypeParameter;
-
-        for (ModelView<?> view : inputs) {
-            Object instance = view.getInstance();
-            if (instance == ignoredInput) {
-                continue;
-            }
-            for (int i = 0; i < args.length; i++) {
-                if (args[i] == null) {
-                    args[i] = instance;
-                    break;
-                }
-            }
-        }
-        ruleDefinition.getRuleInvoker().invoke(args);
-    }
-}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractor.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractor.java
index 6822adf..f3d3093 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractor.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractor.java
@@ -17,18 +17,13 @@
 package org.gradle.platform.base.internal.registry;
 
 import com.google.common.collect.ImmutableList;
-import org.gradle.internal.BiAction;
-import org.gradle.language.base.FunctionalSourceSet;
+import org.gradle.internal.TriAction;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.model.InvalidModelRuleDeclarationException;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.model.internal.core.*;
-import org.gradle.model.internal.core.DelegatingCollectionBuilder;
 import org.gradle.model.internal.inspect.MethodRuleDefinition;
 import org.gradle.model.internal.type.ModelType;
 import org.gradle.platform.base.*;
-import org.gradle.platform.base.internal.BinarySpecInternal;
-import org.gradle.platform.base.internal.ComponentSpecInternal;
 
 import java.util.List;
 
@@ -39,17 +34,17 @@ public class ComponentBinariesModelRuleExtractor extends AbstractAnnotationDrive
         return createRegistration(ruleDefinition);
     }
 
-    private <R, S extends BinarySpec> ExtractedModelRule createRegistration(MethodRuleDefinition<R, ?> ruleDefinition) {
+    private <R, S extends BinarySpec, C extends ComponentSpec> ExtractedModelRule createRegistration(MethodRuleDefinition<R, ?> ruleDefinition) {
         try {
             RuleMethodDataCollector dataCollector = new RuleMethodDataCollector();
             visitAndVerifyMethodSignature(dataCollector, ruleDefinition);
 
             Class<S> binaryType = dataCollector.getParameterType(BinarySpec.class);
-            Class<? extends ComponentSpec> componentType = dataCollector.getParameterType(ComponentSpec.class);
-            ModelReference<CollectionBuilder<BinarySpec>> subject = ModelReference.of(ModelPath.path("binaries"), DefaultCollectionBuilder.typeOf(ModelType.of(BinarySpec.class)));
-            ComponentBinariesRule<R, S> componentBinariesRule = new ComponentBinariesRule<R, S>(subject, componentType, binaryType, ruleDefinition);
+            Class<C> componentType = dataCollector.getParameterType(ComponentSpec.class);
+            ModelReference<ComponentSpecContainer> subject = ModelReference.of(ModelPath.path("components"), ModelType.of(ComponentSpecContainer.class));
+            ComponentBinariesRule<R, S, C> componentBinariesRule = new ComponentBinariesRule<R, S, C>(subject, componentType, binaryType, ruleDefinition);
 
-            return new ExtractedModelAction(ModelActionRole.Mutate, ImmutableList.of(ComponentModelBasePlugin.class), componentBinariesRule);
+            return new ExtractedModelAction(ModelActionRole.Finalize, ImmutableList.of(ComponentModelBasePlugin.class), componentBinariesRule);
         } catch (InvalidModelException e) {
             throw invalidModelRule(ruleDefinition, e);
         }
@@ -57,44 +52,36 @@ public class ComponentBinariesModelRuleExtractor extends AbstractAnnotationDrive
 
     private void visitAndVerifyMethodSignature(RuleMethodDataCollector dataCollector, MethodRuleDefinition<?, ?> ruleDefinition) {
         assertIsVoidMethod(ruleDefinition);
-        visitCollectionBuilderSubject(dataCollector, ruleDefinition, BinarySpec.class);
+        visitSubject(dataCollector, ruleDefinition, BinarySpec.class);
         visitDependency(dataCollector, ruleDefinition, ModelType.of(ComponentSpec.class));
     }
 
-    private class ComponentBinariesRule<R, S extends BinarySpec> extends CollectionBuilderBasedRule<R, S, ComponentSpec, CollectionBuilder<BinarySpec>> {
+    private class ComponentBinariesRule<R, S extends BinarySpec, C extends ComponentSpec> extends ModelMapBasedRule<R, S, ComponentSpec, ComponentSpecContainer> {
 
-        private final Class<? extends ComponentSpec> componentType;
+        private final Class<C> componentType;
         private final Class<S> binaryType;
 
-        public ComponentBinariesRule(ModelReference<CollectionBuilder<BinarySpec>> subject, final Class<? extends ComponentSpec> componentType, final Class<S> binaryType, MethodRuleDefinition<R, ?> ruleDefinition) {
-            super(subject, componentType, ruleDefinition, ModelReference.of(ComponentSpecContainer.class));
+        public ComponentBinariesRule(ModelReference<ComponentSpecContainer> subject, final Class<C> componentType, final Class<S> binaryType, MethodRuleDefinition<R, ?> ruleDefinition) {
+            super(subject, componentType, ruleDefinition);
             this.componentType = componentType;
             this.binaryType = binaryType;
         }
 
-        public void execute(MutableModelNode modelNode, final CollectionBuilder<BinarySpec> binaries, List<ModelView<?>> inputs) {
-            ComponentSpecContainer componentSpecs = ModelViews.assertType(inputs.get(0), ModelType.of(ComponentSpecContainer.class)).getInstance();
-
-            for (final ComponentSpec componentSpec : componentSpecs.withType(componentType)) {
-                CollectionBuilder<S> typed = binaries.withType(binaryType);
-                CollectionBuilder<S> wrapped = new DelegatingCollectionBuilder<S>(typed, ModelType.of(binaryType), new BiAction<String, ModelType<? extends S>>() {
+        public void execute(final MutableModelNode modelNode, final ComponentSpecContainer componentSpecs, final List<ModelView<?>> modelMapRuleInputs) {
+            modelNode.applyToAllLinks(ModelActionRole.Mutate, DirectNodeInputUsingModelAction.of(
+                ModelReference.of(ModelType.of(componentType)),
+                getDescriptor(),
+                getInputs(),
+                new TriAction<MutableModelNode, C, List<ModelView<?>>>() {
                     @Override
-                    public void execute(String s, ModelType<? extends S> modelType) {
-                        BinarySpec binary = binaries.get(s);
-                        assert binary != null : "binary should not be null";
-                        componentSpec.getBinaries().add(binary);
-                        BinarySpecInternal binaryInternal = (BinarySpecInternal) binary;
-                        FunctionalSourceSet binarySourceSet = ((ComponentSpecInternal) componentSpec).getSources().copy(s);
-                        binaryInternal.setBinarySources(binarySourceSet);
+                    public void execute(MutableModelNode componentModelNode, C component, final List<ModelView<?>> componentRuleInputs) {
+                        invoke(componentRuleInputs, component.getBinaries().withType(binaryType), component);
                     }
-                });
-
-                invoke(inputs, wrapped, componentSpec, componentSpecs);
-            }
+                }
+            ));
         }
     }
 
-
     protected InvalidModelRuleDeclarationException invalidModelRule(MethodRuleDefinition<?, ?> ruleDefinition, InvalidModelException e) {
         StringBuilder sb = new StringBuilder();
         ruleDefinition.getDescriptor().describeTo(sb);
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentModelBaseServiceRegistry.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentModelBaseServiceRegistry.java
index 0946085..bbf6f1b 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentModelBaseServiceRegistry.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentModelBaseServiceRegistry.java
@@ -16,7 +16,6 @@
 
 package org.gradle.platform.base.internal.registry;
 
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistration;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.scopes.PluginServiceRegistry;
@@ -58,12 +57,12 @@ public class ComponentModelBaseServiceRegistry implements PluginServiceRegistry
             return new LanguageTypeModelRuleExtractor();
         }
 
-        MethodModelRuleExtractor createComponentModelPluginInspector(Instantiator instantiator) {
-            return new ComponentTypeModelRuleExtractor(instantiator);
+        MethodModelRuleExtractor createComponentModelPluginInspector() {
+            return new ComponentTypeModelRuleExtractor();
         }
 
-        MethodModelRuleExtractor createBinaryTypeModelPluginInspector(Instantiator instantiator) {
-            return new BinaryTypeModelRuleExtractor(instantiator);
+        MethodModelRuleExtractor createBinaryTypeModelPluginInspector() {
+            return new BinaryTypeModelRuleExtractor();
         }
 
         MethodModelRuleExtractor createComponentBinariesPluginInspector() {
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentTypeModelRuleExtractor.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentTypeModelRuleExtractor.java
index b166c65..0c07100 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentTypeModelRuleExtractor.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ComponentTypeModelRuleExtractor.java
@@ -17,11 +17,12 @@
 package org.gradle.platform.base.internal.registry;
 
 import com.google.common.collect.ImmutableList;
-import org.gradle.api.NamedDomainObjectFactory;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.util.BiFunction;
 import org.gradle.language.base.FunctionalSourceSet;
 import org.gradle.language.base.ProjectSourceSet;
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet;
@@ -35,7 +36,7 @@ import org.gradle.platform.base.ComponentSpecIdentifier;
 import org.gradle.platform.base.ComponentType;
 import org.gradle.platform.base.ComponentTypeBuilder;
 import org.gradle.platform.base.component.BaseComponentSpec;
-import org.gradle.platform.base.internal.DefaultComponentSpecContainer;
+import org.gradle.platform.base.internal.ComponentSpecFactory;
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier;
 import org.gradle.platform.base.internal.builder.TypeBuilderInternal;
 
@@ -43,20 +44,16 @@ import java.util.Arrays;
 import java.util.List;
 
 public class ComponentTypeModelRuleExtractor extends TypeModelRuleExtractor<ComponentType, ComponentSpec, BaseComponentSpec> {
-
-    private Instantiator instantiator;
-
-    public ComponentTypeModelRuleExtractor(final Instantiator instantiator) {
+    public ComponentTypeModelRuleExtractor() {
         super("component", ComponentSpec.class, BaseComponentSpec.class, ComponentTypeBuilder.class, JavaReflectionUtil.factory(DirectInstantiator.INSTANCE, DefaultComponentTypeBuilder.class));
-        this.instantiator = instantiator;
     }
 
     @Override
     protected <R, S> ExtractedModelRule createRegistration(MethodRuleDefinition<R, S> ruleDefinition, ModelType<? extends ComponentSpec> type, TypeBuilderInternal<ComponentSpec> builder) {
-        ImmutableList<Class<?>> dependencies = ImmutableList.<Class<?>>of(ComponentModelBasePlugin.class);
+        List<Class<?>> dependencies = ImmutableList.<Class<?>>of(ComponentModelBasePlugin.class);
         ModelType<? extends BaseComponentSpec> implementation = determineImplementationType(type, builder);
         if (implementation != null) {
-            ModelAction<?> mutator = new RegistrationAction(type, implementation, ruleDefinition.getDescriptor(), instantiator);
+            ModelAction<?> mutator = new RegistrationAction(type, implementation, ruleDefinition.getDescriptor());
             return new ExtractedModelAction(ModelActionRole.Defaults, dependencies, mutator);
         }
         return new DependencyOnlyExtractedModelRule(dependencies);
@@ -68,26 +65,22 @@ public class ComponentTypeModelRuleExtractor extends TypeModelRuleExtractor<Comp
         }
     }
 
-    private static class RegistrationAction implements ModelAction<DefaultComponentSpecContainer> {
+    private static class RegistrationAction implements ModelAction<ComponentSpecFactory> {
         private final ModelType<? extends ComponentSpec> publicType;
         private final ModelType<? extends BaseComponentSpec> implementationType;
         private final ModelRuleDescriptor descriptor;
-        private final Instantiator instantiator;
-        private final ModelReference<DefaultComponentSpecContainer> subject;
         private final List<ModelReference<?>> inputs;
 
-        public RegistrationAction(ModelType<? extends ComponentSpec> publicType, ModelType<? extends BaseComponentSpec> implementationType, ModelRuleDescriptor descriptor, Instantiator instantiator) {
+        public RegistrationAction(ModelType<? extends ComponentSpec> publicType, ModelType<? extends BaseComponentSpec> implementationType, ModelRuleDescriptor descriptor) {
             this.publicType = publicType;
             this.implementationType = implementationType;
             this.descriptor = descriptor;
-            this.instantiator = instantiator;
-            this.subject = ModelReference.of(DefaultComponentSpecContainer.class);
-            this.inputs = Arrays.<ModelReference<?>>asList(ModelReference.of(ProjectIdentifier.class), ModelReference.of(ProjectSourceSet.class));
+            this.inputs = Arrays.<ModelReference<?>>asList(ModelReference.of(ServiceRegistry.class), ModelReference.of(ProjectIdentifier.class), ModelReference.of(ProjectSourceSet.class));
         }
 
         @Override
-        public ModelReference<DefaultComponentSpecContainer> getSubject() {
-            return subject;
+        public ModelReference<ComponentSpecFactory> getSubject() {
+            return ModelReference.of(ComponentSpecFactory.class);
         }
 
         @Override
@@ -101,18 +94,21 @@ public class ComponentTypeModelRuleExtractor extends TypeModelRuleExtractor<Comp
         }
 
         @Override
-        public void execute(MutableModelNode modelNode, DefaultComponentSpecContainer components, List<ModelView<?>> inputs) {
-            final ProjectIdentifier projectIdentifier = ModelViews.assertType(inputs.get(0), ModelType.of(ProjectIdentifier.class)).getInstance();
-            final ProjectSourceSet projectSourceSet = ModelViews.assertType(inputs.get(1), ModelType.of(ProjectSourceSet.class)).getInstance();
+        public void execute(MutableModelNode modelNode, ComponentSpecFactory components, List<ModelView<?>> inputs) {
+            ServiceRegistry serviceRegistry = ModelViews.assertType(inputs.get(0), ModelType.of(ServiceRegistry.class)).getInstance();
+            final Instantiator instantiator = serviceRegistry.get(Instantiator.class);
+            final ProjectIdentifier projectIdentifier = ModelViews.assertType(inputs.get(1), ModelType.of(ProjectIdentifier.class)).getInstance();
+            final ProjectSourceSet projectSourceSet = ModelViews.assertType(inputs.get(2), ModelType.of(ProjectSourceSet.class)).getInstance();
             @SuppressWarnings("unchecked")
             Class<ComponentSpec> publicClass = (Class<ComponentSpec>) publicType.getConcreteClass();
-            components.registerFactory(publicClass, new NamedDomainObjectFactory<BaseComponentSpec>() {
-                public BaseComponentSpec create(String name) {
+            components.register(publicClass, descriptor, new BiFunction<ComponentSpec, String, MutableModelNode>() {
+                @Override
+                public ComponentSpec apply(String name, MutableModelNode modelNode) {
                     FunctionalSourceSet componentSourceSet = instantiator.newInstance(DefaultFunctionalSourceSet.class, name, instantiator, projectSourceSet);
                     ComponentSpecIdentifier id = new DefaultComponentSpecIdentifier(projectIdentifier.getPath(), name);
-                    return BaseComponentSpec.create(implementationType.getConcreteClass(), id, componentSourceSet, instantiator);
+                    return BaseComponentSpec.create(implementationType.getConcreteClass(), id, modelNode, componentSourceSet, instantiator);
                 }
-            }, descriptor);
+            });
         }
     }
 }
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ModelMapBasedRule.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ModelMapBasedRule.java
new file mode 100644
index 0000000..c4f7911
--- /dev/null
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/registry/ModelMapBasedRule.java
@@ -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.platform.base.internal.registry;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.api.specs.Spec;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.ModelAction;
+import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.ModelView;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.inspect.MethodRuleDefinition;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class ModelMapBasedRule<R, S, T, C> implements ModelAction<C> {
+    private final ModelReference<C> subject;
+    private final Class<? extends T> baseType;
+    private final MethodRuleDefinition<R, ?> ruleDefinition;
+
+    private ImmutableList<ModelReference<?>> inputs;
+    protected int baseTypeParameterIndex;
+
+    public ModelMapBasedRule(ModelReference<C> subject, Class<? extends T> baseType, MethodRuleDefinition<R, ?> ruleDefinition, ModelReference<?>... additionalInputs) {
+        this.subject = subject;
+        this.baseType = baseType;
+        this.ruleDefinition = ruleDefinition;
+        this.inputs = calculateInputs(Arrays.asList(additionalInputs));
+    }
+
+    public List<ModelReference<?>> getInputs() {
+        return this.inputs;
+    }
+
+    public ModelReference<C> getSubject() {
+        return subject;
+    }
+
+    public ModelRuleDescriptor getDescriptor() {
+        return ruleDefinition.getDescriptor();
+    }
+
+    private ImmutableList<ModelReference<?>> calculateInputs(List<ModelReference<?>> modelReferences) {
+        final List<ModelReference<?>> references = this.ruleDefinition.getReferences().subList(1, this.ruleDefinition.getReferences().size());
+        final List<ModelReference<?>> filteredReferences = CollectionUtils.filter(references, new Spec<ModelReference<?>>() {
+            public boolean isSatisfiedBy(ModelReference<?> element) {
+                if (element.getType().equals(ModelType.of(baseType))) {
+                    baseTypeParameterIndex = references.indexOf(element) + 1;
+                    return false;
+                }
+                return true;
+            }
+        });
+
+        ImmutableList.Builder<ModelReference<?>> allInputs = ImmutableList.builder();
+        allInputs.addAll(modelReferences);
+        allInputs.addAll(filteredReferences);
+        return allInputs.build();
+    }
+
+    protected void invoke(List<ModelView<?>> inputs, ModelMap<S> modelMap, T baseTypeParameter, Object... ignoredInputs) {
+        List<Object> ignoredInputsList = Arrays.asList(ignoredInputs);
+        Object[] args = new Object[inputs.size() + 2 - ignoredInputs.length];
+        args[0] = modelMap;
+        args[baseTypeParameterIndex] = baseTypeParameter;
+
+        for (ModelView<?> view : inputs) {
+            Object instance = view.getInstance();
+            if (ignoredInputsList.contains(instance)) {
+                continue;
+            }
+            for (int i = 0; i < args.length; i++) {
+                if (args[i] == null) {
+                    args[i] = instance;
+                    break;
+                }
+            }
+        }
+        ruleDefinition.getRuleInvoker().invoke(args);
+    }
+}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainer.java
deleted file mode 100644
index 33f2f27..0000000
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.platform.base.internal.rules;
-
-import com.google.common.collect.Maps;
-import org.gradle.api.GradleException;
-import org.gradle.api.NamedDomainObjectFactory;
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
-
-import java.util.Map;
-
-public abstract class RuleAwarePolymorphicDomainObjectContainer<T> extends DefaultPolymorphicDomainObjectContainer<T> {
-    private final Map<Class<? extends T>, ModelRuleDescriptor> creators = Maps.newHashMap();
-
-    public RuleAwarePolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
-        super(type, instantiator);
-    }
-
-    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory, ModelRuleDescriptor descriptor) {
-        checkCanRegister(type, descriptor);
-        super.registerFactory(type, factory);
-    }
-
-    private void checkCanRegister(Class<? extends T> type, ModelRuleDescriptor descriptor) {
-        ModelRuleDescriptor creator = creators.get(type);
-        if (creator != null) {
-            StringBuilder builder = new StringBuilder("Cannot register a factory for type ")
-                    .append(type.getSimpleName())
-                    .append(" because a factory for this type was already registered by ");
-            creator.describeTo(builder);
-            builder.append(".");
-            throw new GradleException(builder.toString());
-        }
-        creators.put(type, descriptor);
-    }
-}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/test/DefaultTestSuiteContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/test/DefaultTestSuiteContainer.java
deleted file mode 100644
index a25d778..0000000
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/internal/test/DefaultTestSuiteContainer.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.platform.base.internal.test;
-
-import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.platform.base.test.TestSuiteContainer;
-import org.gradle.platform.base.test.TestSuiteSpec;
-
-public class DefaultTestSuiteContainer extends DefaultPolymorphicDomainObjectContainer<TestSuiteSpec> implements TestSuiteContainer {
-    public DefaultTestSuiteContainer(Instantiator instantiator) {
-        super(TestSuiteSpec.class, instantiator);
-    }
-}
diff --git a/subprojects/platform-base/src/main/java/org/gradle/platform/base/test/TestSuiteContainer.java b/subprojects/platform-base/src/main/java/org/gradle/platform/base/test/TestSuiteContainer.java
index b0cd1eb..5abf7d1 100644
--- a/subprojects/platform-base/src/main/java/org/gradle/platform/base/test/TestSuiteContainer.java
+++ b/subprojects/platform-base/src/main/java/org/gradle/platform/base/test/TestSuiteContainer.java
@@ -17,11 +17,11 @@
 package org.gradle.platform.base.test;
 
 import org.gradle.api.Incubating;
-import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.model.ModelMap;
 
 /**
  * A container of {@link TestSuiteSpec} instances.
  */
 @Incubating
-public interface TestSuiteContainer extends NamedDomainObjectContainer<TestSuiteSpec> {
+public interface TestSuiteContainer extends ModelMap<TestSuiteSpec> {
 }
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/ComponentTypeModelRuleExtractorTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/ComponentTypeModelRuleExtractorTest.groovy
index 3b91dc5..c3d97c4 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/ComponentTypeModelRuleExtractorTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/ComponentTypeModelRuleExtractorTest.groovy
@@ -16,16 +16,15 @@
 
 package org.gradle.language.base.internal
 
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.language.base.plugins.ComponentModelBasePlugin
 import org.gradle.model.InvalidModelRuleDeclarationException
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelActionRole
 import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.type.ModelType
 import org.gradle.platform.base.*
 import org.gradle.platform.base.component.BaseComponentSpec
-import org.gradle.platform.base.internal.DefaultComponentSpecContainer
+import org.gradle.platform.base.internal.ComponentSpecFactory
 import org.gradle.platform.base.internal.registry.AbstractAnnotationModelRuleExtractorTest
 import org.gradle.platform.base.internal.registry.ComponentTypeModelRuleExtractor
 import spock.lang.Unroll
@@ -33,9 +32,8 @@ import spock.lang.Unroll
 import java.lang.annotation.Annotation
 
 class ComponentTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExtractorTest {
-    Instantiator instantiator = DirectInstantiator.INSTANCE
-
-    ComponentTypeModelRuleExtractor ruleHandler = new ComponentTypeModelRuleExtractor(instantiator)
+    final static ModelType<ComponentSpecFactory> FACTORY_REGISTRY_TYPE = ModelType.of(ComponentSpecFactory)
+    ComponentTypeModelRuleExtractor ruleHandler = new ComponentTypeModelRuleExtractor()
 
     @Override
     Class<? extends Annotation> getAnnotation() { return ComponentType }
@@ -50,7 +48,7 @@ class ComponentTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExt
         registration.ruleDependencies == [ComponentModelBasePlugin]
         registration.type == ExtractedModelRule.Type.ACTION
         registration.actionRole == ModelActionRole.Defaults
-        registration.action.subject == ModelReference.of(DefaultComponentSpecContainer)
+        registration.action.subject == ModelReference.of(FACTORY_REGISTRY_TYPE)
     }
 
     def "applies ComponentModelBasePlugin only when implementation not set"() {
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/registry/LanguageTypeModelRuleExtractorTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/registry/LanguageTypeModelRuleExtractorTest.groovy
index efd34f1..23883a2 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/registry/LanguageTypeModelRuleExtractorTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/internal/registry/LanguageTypeModelRuleExtractorTest.groovy
@@ -15,7 +15,6 @@
  */
 
 package org.gradle.language.base.internal.registry
-
 import org.gradle.api.Action
 import org.gradle.api.Task
 import org.gradle.api.file.SourceDirectorySet
@@ -66,8 +65,8 @@ class LanguageTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExtr
         "returnValue"                       | "Method annotated with @LanguageType must not have a return value."                                                          | "non void method"
         "noParams"                          | "Method annotated with @LanguageType must have a single parameter of type '${LanguageTypeBuilder.name}'."                    | "no LanguageTypeBuilder subject"
         "wrongSubject"                      | "Method annotated with @LanguageType must have a single parameter of type '${LanguageTypeBuilder.name}'."                    | "wrong rule subject type"
-        "rawLanguageTypeBuilder"            | "Parameter of type 'org.gradle.platform.base.LanguageTypeBuilder' must declare a type parameter."                            | "non typed CollectionBuilder parameter"
-        "wildcardLanguageTypeBuilder"       | "Language type '?' cannot be a wildcard type (i.e. cannot use ? super, ? extends etc.)."                                     | "wild card CollectionBuilder parameter"
+        "rawLanguageTypeBuilder"            | "Parameter of type 'org.gradle.platform.base.LanguageTypeBuilder' must declare a type parameter."                            | "non typed ModelMap parameter"
+        "wildcardLanguageTypeBuilder"       | "Language type '?' cannot be a wildcard type (i.e. cannot use ? super, ? extends etc.)."                                     | "wild card ModelMap parameter"
         "notImplementingLibraryType"        | "Language implementation '${NotImplementingCustomLanguageSourceSet.name}' must implement '${CustomLanguageSourceSet.name}'." | "implementation not implementing type class"
         "wrongSubType"                      | "Language type 'java.lang.String' is not a subtype of 'org.gradle.language.base.LanguageSourceSet'."                         | "implementation not extending BaseComponentSpec"
         "notExtendingBaseLanguageSourceSet" | "Language implementation '${NotExtendingBaseLanguageSourceSet.name}' must extend '${BaseLanguageSourceSet.name}'."           | "implementation not extending ${BaseLanguageSourceSet.name}"
@@ -122,7 +121,7 @@ class LanguageTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExtr
 
         @Override
         void generatedBy(Task generatorTask) {
-            }
+        }
 
         @Override
         String getName() {
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginTest.groovy
deleted file mode 100644
index 037b4a7..0000000
--- a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/ComponentModelBasePluginTest.groovy
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * 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.plugins
-
-import org.gradle.api.Action
-import org.gradle.api.NamedDomainObjectFactory
-import org.gradle.api.Task
-import org.gradle.api.file.FileCollection
-import org.gradle.api.file.SourceDirectorySet
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.tasks.TaskDependency
-import org.gradle.language.base.FunctionalSourceSet
-import org.gradle.language.base.LanguageSourceSet
-import org.gradle.language.base.internal.SourceTransformTaskConfig
-import org.gradle.language.base.internal.registry.LanguageRegistration
-import org.gradle.language.base.internal.registry.LanguageTransform
-import org.gradle.model.internal.core.ModelPath
-import org.gradle.platform.base.BinarySpec
-import org.gradle.platform.base.TransformationFileType
-import org.gradle.platform.base.component.BaseComponentSpec
-import org.gradle.platform.base.internal.ComponentSpecInternal
-import org.gradle.util.TestUtil
-import org.gradle.util.WrapUtil
-import spock.lang.Specification
-
-class ComponentModelBasePluginTest extends Specification {
-    def project = TestUtil.createRootProject()
-
-    def "adds componentSpecs extension"() {
-        when:
-        project.pluginManager.apply(ComponentModelBasePlugin)
-        then:
-        project.componentSpecs != null
-    }
-
-    def "adds componentSpecs model"() {
-        when:
-        project.pluginManager.apply(ComponentModelBasePlugin)
-        then:
-        project.modelRegistry.get(ModelPath.path("components")) != null
-    }
-
-    def "registers language sourceset factory and created default source set for component"() {
-        setup:
-
-        def componentSpecInternal = Mock(ComponentSpecInternal)
-        _ * componentSpecInternal.name >> "testComponent"
-        _ * componentSpecInternal.inputTypes >> [TestTransformFile.class]
-
-        def componentFunctionalSourceSet = Mock(FunctionalSourceSet)
-        _ * componentFunctionalSourceSet.name >> "testComponentSources"
-        _ * componentSpecInternal.sources >> componentFunctionalSourceSet
-        _ * componentSpecInternal.source >> WrapUtil.toDomainObjectSet(LanguageSourceSet)
-
-        when:
-        project.pluginManager.apply(ComponentModelBasePlugin)
-        project.model {
-            languages {
-                add(new TestLanguageRegistration())
-            }
-            languageTransforms {
-                add(new TestLanguageRegistration())
-            }
-        }
-        project.componentSpecs.add(componentSpecInternal)
-        project.tasks.realize()
-
-        then:
-        1 * componentFunctionalSourceSet.registerFactory(TestSourceSet, _ as NamedDomainObjectFactory)
-        1 * componentFunctionalSourceSet.maybeCreate("test", TestSourceSet)
-        1 * componentFunctionalSourceSet.getName() >> "testFunctionalSourceSet"
-        0 * componentFunctionalSourceSet._
-    }
-
-    public static class TestLanguageRegistration implements LanguageRegistration, LanguageTransform {
-        @Override
-        String getName() {
-            return "test"
-        }
-
-        @Override
-        Class getSourceSetType() {
-            return TestSourceSet.class
-        }
-
-        @Override
-        Map<String, Class<?>> getBinaryTools() {
-            return null
-        }
-
-        @Override
-        Class<? extends TransformationFileType> getOutputType() {
-            return TestTransformFile.class
-        }
-
-        @Override
-        SourceTransformTaskConfig getTransformTask() {
-            return null
-        }
-
-        @Override
-        boolean applyToBinary(BinarySpec binary) {
-            return false
-        }
-
-        @Override
-        public NamedDomainObjectFactory getSourceSetFactory(String parentName) {
-            return new NamedDomainObjectFactory() {
-                @Override
-                Object create(String name) {
-                    new TestSourceImplementation(name, parentName, fileResolver)
-                }
-            }
-        }
-    }
-
-    public static class TestSourceImplementation implements TestSourceSet {
-        String name
-
-        public TestSourceImplementation(String name, String parent, FileResolver fileResolver) {
-            this.name = name;
-        }
-
-        @Override
-        String getName() {
-            return name;
-        }
-
-        FileCollection getCompileClasspath() {
-            return null
-        }
-
-        void setCompileClasspath(FileCollection classpath) {
-
-        }
-
-        FileCollection getRuntimeClasspath() {
-            return null
-        }
-
-        void setRuntimeClasspath(FileCollection classpath) {
-
-        }
-
-        def getOutput() {
-            return null
-        }
-
-        TestSourceSet compiledBy(Object... taskPaths) {
-            return null
-        }
-
-        SourceDirectorySet getResources() {
-            return null
-        }
-
-        TestSourceSet resources(Closure configureClosure) {
-            return null
-        }
-
-        SourceDirectorySet getJava() {
-            return null
-        }
-
-        TestSourceSet java(Closure configureClosure) {
-            return null
-        }
-
-        SourceDirectorySet getAllJava() {
-            return null
-        }
-
-        SourceDirectorySet getAllSource() {
-            return null
-        }
-
-        String getClassesTaskName() {
-            return null
-        }
-
-        String getProcessResourcesTaskName() {
-            return null
-        }
-
-        String getCompileJavaTaskName() {
-            return null
-        }
-
-        String getCompileTaskName(String language) {
-            return null
-        }
-
-        String getJarTaskName() {
-            return null
-        }
-
-        String getTaskName(String verb, String target) {
-            return null
-        }
-
-        String getCompileConfigurationName() {
-            return null
-        }
-
-        String getRuntimeConfigurationName() {
-            return null
-        }
-
-        String getDisplayName() {
-            return null
-        }
-
-        SourceDirectorySet getSource() {
-            return null
-        }
-
-        @Override
-        void source(Action<? super SourceDirectorySet> config) {
-
-        }
-
-        @Override
-        void generatedBy(Task generatorTask) {
-
-        }
-
-        @Override
-        Task getBuildTask() {
-            return null
-        }
-
-        @Override
-        void setBuildTask(Task lifecycleTask) {
-
-        }
-
-        @Override
-        void builtBy(Object... tasks) {
-
-        }
-
-        @Override
-        boolean hasBuildDependencies() {
-            return false
-        }
-
-        @Override
-        TaskDependency getBuildDependencies() {
-            return null
-        }
-    }
-
-    public static class TestTransformFile implements TransformationFileType {
-
-    }
-
-    public static interface TestSourceSet extends LanguageSourceSet {
-    }
-
-    public static class TestComponentSpecInternal extends BaseComponentSpec implements ComponentSpecInternal {
-        @Override
-        Set<Class<? extends TransformationFileType>> getInputTypes() {
-            return new HashSet<Class<? extends TransformationFileType>>(0)
-        }
-    }
-
-    public static class TestComponentSpec extends BaseComponentSpec {
-    }
-}
\ No newline at end of file
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/LifecycleBasePluginTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/LifecycleBasePluginTest.groovy
index 247cdf0..cf8c3b4 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/LifecycleBasePluginTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/language/base/plugins/LifecycleBasePluginTest.groovy
@@ -38,6 +38,7 @@ class LifecycleBasePluginTest extends Specification {
         def clean = project.tasks[CLEAN_TASK_NAME]
         clean instanceOf(Delete)
         clean dependsOn()
+        clean.group == BUILD_GROUP
         clean.targetFiles.files == [project.buildDir] as Set
 
         and:
@@ -52,7 +53,7 @@ class LifecycleBasePluginTest extends Specification {
 
         and:
         def build = project.tasks[BUILD_TASK_NAME]
-        build.group == LifecycleBasePlugin.BUILD_GROUP
+        build.group == BUILD_GROUP
         build dependsOn(ASSEMBLE_TASK_NAME, CHECK_TASK_NAME)
         check instanceOf(DefaultTask)
     }
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/component/BaseComponentSpecTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/component/BaseComponentSpecTest.groovy
index f69494b..2775009 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/component/BaseComponentSpecTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/component/BaseComponentSpecTest.groovy
@@ -15,19 +15,23 @@
  */
 
 package org.gradle.platform.base.component
+
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.base.LanguageSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.platform.base.ComponentSpecIdentifier
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.platform.base.ModelInstantiationException
+import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
 class BaseComponentSpecTest extends Specification {
     def instantiator = DirectInstantiator.INSTANCE
-    def componentId = Mock(ComponentSpecIdentifier)
-    FunctionalSourceSet functionalSourceSet;
+    def componentId = new DefaultComponentSpecIdentifier("p", "c")
+    FunctionalSourceSet functionalSourceSet
+
+    def modelRegistry = new ModelRegistryHelper()
 
     def setup() {
         functionalSourceSet = new DefaultFunctionalSourceSet("testFSS", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet));
@@ -44,31 +48,32 @@ class BaseComponentSpecTest extends Specification {
 
     def "cannot create instance of base class"() {
         when:
-        BaseComponentSpec.create(BaseComponentSpec, componentId, functionalSourceSet, instantiator)
+        create(BaseComponentSpec)
 
         then:
         def e = thrown ModelInstantiationException
         e.message == "Cannot create instance of abstract class BaseComponentSpec."
     }
 
-    def "library has name, path and sensible display name"() {
-        def component = BaseComponentSpec.create(MySampleComponent, componentId, functionalSourceSet, instantiator)
+    private <T extends BaseComponentSpec> T create(Class<T> type) {
+        BaseComponentFixtures.create(type, modelRegistry, componentId, functionalSourceSet, instantiator)
+    }
 
+    def "library has name, path and sensible display name"() {
         when:
-        _ * componentId.name >> "jvm-lib"
-        _ * componentId.projectPath >> ":project-path"
+        def component = create(MySampleComponent)
 
         then:
         component.class == MySampleComponent
-        component.name == "jvm-lib"
-        component.projectPath == ":project-path"
-        component.displayName == "MySampleComponent 'jvm-lib'"
+        component.name == componentId.name
+        component.projectPath == componentId.projectPath
+        component.displayName == "MySampleComponent '$componentId.name'"
     }
 
     def "create fails if subtype does not have a public no-args constructor"() {
 
         when:
-        BaseComponentSpec.create(MyConstructedComponent, componentId, functionalSourceSet, instantiator)
+        create(MyConstructedComponent)
 
         then:
         def e = thrown ModelInstantiationException
@@ -82,14 +87,14 @@ class BaseComponentSpecTest extends Specification {
         def lss1 = languageSourceSet("lss1")
         functionalSourceSet.add(lss1)
 
-        def component = BaseComponentSpec.create(MySampleComponent, componentId, functionalSourceSet, instantiator)
+        def component = create(MySampleComponent)
 
         and:
         def lss2 = languageSourceSet("lss2")
         functionalSourceSet.add(lss2)
 
         then:
-        component.getSource() as List == [lss1, lss2]
+        component.getSource().values() as List == [lss1, lss2]
     }
 
     def languageSourceSet(String name) {
@@ -99,6 +104,7 @@ class BaseComponentSpecTest extends Specification {
     }
 
     static class MySampleComponent extends BaseComponentSpec {}
+
     static class MyConstructedComponent extends BaseComponentSpec {
         MyConstructedComponent(String arg) {}
     }
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractorTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractorTest.groovy
index 57e0d23..a42c86a 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractorTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTasksModelRuleExtractorTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.platform.base.internal.registry
 import org.gradle.api.Task
 import org.gradle.language.base.plugins.ComponentModelBasePlugin
 import org.gradle.model.InvalidModelRuleDeclarationException
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelActionRole
 import org.gradle.model.internal.core.ModelReference
@@ -56,12 +56,12 @@ class BinaryTasksModelRuleExtractorTest extends AbstractAnnotationModelRuleExtra
         ex.cause.message == expectedMessage
 
         where:
-        methodName             | expectedMessage                                                                                                             | descr
-        "returnValue"          | "Method annotated with @BinaryTasks must not have a return value."                                                          | "non void method"
-        "noParams"             | "Method annotated with @BinaryTasks must have a parameter of type '${CollectionBuilder.name}'."                             | "no CollectionBuilder subject"
-        "wrongSubject"         | "Method annotated with @BinaryTasks first parameter must be of type '${CollectionBuilder.name}'."                           | "wrong rule subject type"
-        "noBinaryParameter"    | "Method annotated with @BinaryTasks must have one parameter extending BinarySpec. Found no parameter extending BinarySpec." | "no component spec parameter"
-        "rawCollectionBuilder" | "Parameter of type 'CollectionBuilder' must declare a type parameter extending 'Task'."                                     | "non typed CollectionBuilder parameter"
+        methodName          | expectedMessage                                                                                                             | descr
+        "returnValue"       | "Method annotated with @BinaryTasks must not have a return value."                                                          | "non void method"
+        "noParams"          | "Method annotated with @BinaryTasks must have a parameter of type '${ModelMap.name}'."                                      | "no ModelMap subject"
+        "wrongSubject"      | "Method annotated with @BinaryTasks first parameter must be of type '${ModelMap.name}'."                                    | "wrong rule subject type"
+        "noBinaryParameter" | "Method annotated with @BinaryTasks must have one parameter extending BinarySpec. Found no parameter extending BinarySpec." | "no component spec parameter"
+        "rawModelMap"       | "Parameter of type '${ModelMap.simpleName}' must declare a type parameter extending 'Task'."                                | "non typed ModelMap parameter"
     }
 
     @Unroll
@@ -81,7 +81,7 @@ class BinaryTasksModelRuleExtractorTest extends AbstractAnnotationModelRuleExtra
     static class Rules {
 
         @BinaryTasks
-        static String returnValue(CollectionBuilder<Task> builder, SomeBinary binary) {
+        static String returnValue(ModelMap<Task> builder, SomeBinary binary) {
         }
 
         @BinaryTasks
@@ -93,16 +93,16 @@ class BinaryTasksModelRuleExtractorTest extends AbstractAnnotationModelRuleExtra
         }
 
         @BinaryTasks
-        static void rawCollectionBuilder(CollectionBuilder tasks, SomeBinary binary) {
+        static void rawModelMap(ModelMap tasks, SomeBinary binary) {
         }
 
         @BinaryTasks
-        static void noBinaryParameter(CollectionBuilder<Task> builder) {
+        static void noBinaryParameter(ModelMap<Task> builder) {
         }
 
         @BinaryTasks
-        static void validTypeRule(CollectionBuilder<Task> tasks, SomeBinary binary) {
+        static void validTypeRule(ModelMap<Task> tasks, SomeBinary binary) {
             tasks.create("create${binary.getName()}")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractorTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractorTest.groovy
index a20e4fc..b848f4a 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractorTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/BinaryTypeModelRuleExtractorTest.groovy
@@ -16,28 +16,24 @@
 
 package org.gradle.platform.base.internal.registry
 
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.internal.model.BinarySpecFactoryRegistry
 import org.gradle.language.base.plugins.ComponentModelBasePlugin
 import org.gradle.model.InvalidModelRuleDeclarationException
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelActionRole
 import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.type.ModelType
 import org.gradle.platform.base.BinarySpec
 import org.gradle.platform.base.BinaryType
 import org.gradle.platform.base.BinaryTypeBuilder
 import org.gradle.platform.base.InvalidModelException
 import org.gradle.platform.base.binary.BaseBinarySpec
-import org.gradle.platform.base.internal.DefaultBinaryContainer
 import spock.lang.Unroll
 
 import java.lang.annotation.Annotation
 
 class BinaryTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExtractorTest {
-
-    Instantiator instantiator = DirectInstantiator.INSTANCE
-
-    BinaryTypeModelRuleExtractor ruleHandler = new BinaryTypeModelRuleExtractor(instantiator)
+    BinaryTypeModelRuleExtractor ruleHandler = new BinaryTypeModelRuleExtractor()
 
     @Override
     Class<? extends Annotation> getAnnotation() {
@@ -54,7 +50,7 @@ class BinaryTypeModelRuleExtractorTest extends AbstractAnnotationModelRuleExtrac
         registration.ruleDependencies == [ComponentModelBasePlugin]
         registration.type == ExtractedModelRule.Type.ACTION
         registration.actionRole == ModelActionRole.Defaults
-        registration.action.subject == ModelReference.of(DefaultBinaryContainer)
+        registration.action.subject == ModelReference.of(ModelType.of(BinarySpecFactoryRegistry))
     }
 
     def "applies ComponentModelBasePlugin only when implementation not set"() {
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractorTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractorTest.groovy
index 0087370..3158e5f 100644
--- a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractorTest.groovy
+++ b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/registry/ComponentBinariesModelRuleExtractorTest.groovy
@@ -18,11 +18,11 @@ package org.gradle.platform.base.internal.registry
 
 import org.gradle.language.base.plugins.ComponentModelBasePlugin
 import org.gradle.model.InvalidModelRuleDeclarationException
-import org.gradle.model.collection.CollectionBuilder
-import org.gradle.model.internal.core.DefaultCollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.model.internal.core.ExtractedModelRule
 import org.gradle.model.internal.core.ModelActionRole
 import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.type.ModelType
 import org.gradle.platform.base.*
 import spock.lang.Unroll
 
@@ -47,8 +47,8 @@ class ComponentBinariesModelRuleExtractorTest extends AbstractAnnotationModelRul
         then:
         registration.ruleDependencies == [ComponentModelBasePlugin]
         registration.type == ExtractedModelRule.Type.ACTION
-        registration.actionRole == ModelActionRole.Mutate
-        registration.action.subject == ModelReference.of("binaries", DefaultCollectionBuilder.typeOf(BinarySpec))
+        registration.actionRole == ModelActionRole.Finalize
+        registration.action.subject == ModelReference.of("components", ModelType.of(ComponentSpecContainer))
 
         where:
         ruleName         | descr
@@ -73,12 +73,12 @@ class ComponentBinariesModelRuleExtractorTest extends AbstractAnnotationModelRul
 
         where:
         methodName                | expectedMessage                                                                                                                               | descr
-        "noParams"                | "Method annotated with @ComponentBinaries must have a parameter of type '${CollectionBuilder.name}'."                                         | "no CollectionBuilder parameter"
-        "wrongSubject"            | "Method annotated with @ComponentBinaries first parameter must be of type '${CollectionBuilder.name}'."                                       | "wrong rule subject type"
+        "noParams"                | "Method annotated with @ComponentBinaries must have a parameter of type '${ModelMap.name}'."                                                  | "no ModelMap parameter"
+        "wrongSubject"            | "Method annotated with @ComponentBinaries first parameter must be of type '${ModelMap.name}'."                                                | "wrong rule subject type"
         "multipileComponentSpecs" | "Method annotated with @ComponentBinaries must have one parameter extending ComponentSpec. Found multiple parameter extending ComponentSpec." | "additional component spec parameter"
         "noComponentSpec"         | "Method annotated with @ComponentBinaries must have one parameter extending ComponentSpec. Found no parameter extending ComponentSpec."       | "no component spec parameter"
         "returnValue"             | "Method annotated with @ComponentBinaries must not have a return value."                                                                      | "non void method"
-        "rawCollectionBuilder"    | "Parameter of type 'CollectionBuilder' must declare a type parameter extending 'BinarySpec'."                                                 | "non typed CollectionBuilder parameter"
+        "rawModelMap"             | "Parameter of type '${ModelMap.simpleName}' must declare a type parameter extending 'BinarySpec'."                                            | "non typed ModelMap parameter"
     }
 
     interface SomeBinarySpec extends BinarySpec {}
@@ -95,22 +95,22 @@ class ComponentBinariesModelRuleExtractorTest extends AbstractAnnotationModelRul
         }
 
         @ComponentBinaries
-        static void validTypeRule(CollectionBuilder<SomeBinarySpec> binaries, SomeLibrary library) {
+        static void validTypeRule(ModelMap<SomeBinarySpec> binaries, SomeLibrary library) {
             binaries.create("${library.name}Binary", library)
         }
 
         @ComponentBinaries
-        static void rawBinarySpec(CollectionBuilder<BinarySpec> binaries, RawLibrary library) {
+        static void rawBinarySpec(ModelMap<BinarySpec> binaries, RawLibrary library) {
             binaries.create("${library.name}Binary", library)
         }
 
         @ComponentBinaries
-        static void rawCollectionBuilder(CollectionBuilder binaries, RawLibrary library) {
+        static void rawModelMap(ModelMap binaries, RawLibrary library) {
             binaries.create("${library.name}Binary", library)
         }
 
         @ComponentBinaries
-        static void librarySubType(CollectionBuilder<SomeBinarySubType> binaries, SomeLibrary library) {
+        static void librarySubType(ModelMap<SomeBinarySubType> binaries, SomeLibrary library) {
             binaries.create("${library.name}Binary", library)
         }
 
@@ -119,16 +119,16 @@ class ComponentBinariesModelRuleExtractorTest extends AbstractAnnotationModelRul
         }
 
         @ComponentBinaries
-        static void multipileComponentSpecs(CollectionBuilder<SomeBinarySpec> binaries, SomeLibrary library, SomeLibrary otherLibrary) {
+        static void multipileComponentSpecs(ModelMap<SomeBinarySpec> binaries, SomeLibrary library, SomeLibrary otherLibrary) {
             binaries.create("${library.name}Binary", library)
         }
 
         @ComponentBinaries
-        static void noComponentSpec(CollectionBuilder<SomeBinarySpec> binaries) {
+        static void noComponentSpec(ModelMap<SomeBinarySpec> binaries) {
         }
 
         @ComponentBinaries
         static String returnValue(BinaryTypeBuilder<SomeBinarySpec> builder) {
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy b/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy
deleted file mode 100644
index e21b688..0000000
--- a/subprojects/platform-base/src/test/groovy/org/gradle/platform/base/internal/rules/RuleAwarePolymorphicDomainObjectContainerTest.groovy
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.platform.base.internal.rules
-
-import org.gradle.api.GradleException
-import org.gradle.api.Named
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor
-import spock.lang.Specification
-
-class RuleAwarePolymorphicDomainObjectContainerTest extends Specification {
-
-    def container = new DummyContainer()
-
-    def "reports duplicate type registration that was created without rule context"() {
-        given:
-        container.registerFactory(Dummy, { new Dummy("foo") })
-
-        when:
-        container.registerFactory(Dummy, { new Dummy("other") })
-
-        then:
-        def t = thrown GradleException
-        t.message == "Cannot register a factory for type Dummy because a factory for this type is already registered."
-    }
-
-    def "reports duplicate type registration that was created with rule context"() {
-        given:
-        container.registerFactory(Dummy, { new Dummy(it) }, new SimpleModelRuleDescriptor("<model-rule>"))
-
-        when:
-        container.registerFactory(Dummy, { new Dummy(it) }, new SimpleModelRuleDescriptor("<other-rule>"))
-
-        then:
-        def t = thrown GradleException
-        t.message == "Cannot register a factory for type Dummy because a factory for this type was already registered by <model-rule>."
-    }
-
-    class DummyContainer extends RuleAwarePolymorphicDomainObjectContainer<Object> {
-        DummyContainer() {
-            super(Dummy.class, DirectInstantiator.INSTANCE)
-        }
-    }
-
-    class Dummy implements Named {
-        String name
-
-        Dummy(String name) {
-            this.name = name
-        }
-    }
-}
diff --git a/subprojects/platform-base/src/testFixtures/groovy/org/gradle/platform/base/component/BaseComponentFixtures.groovy b/subprojects/platform-base/src/testFixtures/groovy/org/gradle/platform/base/component/BaseComponentFixtures.groovy
new file mode 100644
index 0000000..3399ce8
--- /dev/null
+++ b/subprojects/platform-base/src/testFixtures/groovy/org/gradle/platform/base/component/BaseComponentFixtures.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.platform.base.component
+
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.base.FunctionalSourceSet
+import org.gradle.model.internal.core.ModelCreators
+import org.gradle.model.internal.core.ModelNode
+import org.gradle.model.internal.core.ModelReference
+import org.gradle.model.internal.core.ModelRuleExecutionException
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.platform.base.ComponentSpecIdentifier
+
+class BaseComponentFixtures {
+
+    static <T extends BaseComponentSpec> T create(Class<T> type, ModelRegistryHelper modelRegistry, ComponentSpecIdentifier componentId, FunctionalSourceSet functionalSourceSet, Instantiator instantiator) {
+        try {
+            modelRegistry.create(
+                ModelCreators.unmanagedInstanceOf(ModelReference.of(componentId.name, type), {
+                    BaseComponentSpec.create(type, componentId, it, functionalSourceSet, instantiator)
+                })
+                    .descriptor(componentId.name)
+                    .build()
+            ).atState(componentId.name, ModelNode.State.Initialized).getPrivateData(type)
+        } catch (ModelRuleExecutionException e) {
+            throw e.cause
+        }
+    }
+
+}
diff --git a/subprojects/platform-jvm/platform-jvm.gradle b/subprojects/platform-jvm/platform-jvm.gradle
index c79454b..b036bc2 100644
--- a/subprojects/platform-jvm/platform-jvm.gradle
+++ b/subprojects/platform-jvm/platform-jvm.gradle
@@ -12,6 +12,7 @@ dependencies {
 
 useTestFixtures()
 useTestFixtures(project: ':diagnostics')
+useTestFixtures(project: ':platformBase')
 
 useClassycle()
 strictCompile()
diff --git a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ComponentReportIntegrationTest.groovy b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ComponentReportIntegrationTest.groovy
index f89d1c7..c8ecdc9 100644
--- a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ComponentReportIntegrationTest.groovy
+++ b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ComponentReportIntegrationTest.groovy
@@ -35,7 +35,17 @@ plugins {
 
 model {
     components {
-        someLib(JvmLibrarySpec)
+        someLib(JvmLibrarySpec) {
+            sources {
+                java {
+                    dependencies {
+                        library 'library-only'
+                        project 'project-only'
+                        library 'some-library' project 'some-project'
+                    }
+                }
+            }
+        }
     }
 }
 """
@@ -49,9 +59,13 @@ JVM library 'someLib'
 
 Source sets
     Java source 'someLib:java'
-        src/someLib/java
+        srcDir: src/someLib/java
+        dependencies
+            library 'library-only'
+            project 'project-only'
+            project 'some-project' library 'some-library'
     JVM resources 'someLib:resources'
-        src/someLib/resources
+        srcDir: src/someLib/resources
 
 Binaries
     Jar 'someLibJar'
@@ -89,9 +103,9 @@ JVM library 'myLib'
 
 Source sets
     Java source 'myLib:java'
-        src/myLib/java
+        srcDir: src/myLib/java
     JVM resources 'myLib:resources'
-        src/myLib/resources
+        srcDir: src/myLib/resources
 
 Binaries
     Jar 'java5MyLibJar'
@@ -143,9 +157,9 @@ JVM library 'myLib'
 
 Source sets
     Java source 'myLib:java'
-        src/myLib/java
+        srcDir: src/myLib/java
     JVM resources 'myLib:resources'
-        src/myLib/resources
+        srcDir: src/myLib/resources
 
 Binaries
     Jar 'java5MyLibJar'
@@ -170,9 +184,9 @@ JVM library 'myLib2'
 
 Source sets
     Java source 'myLib2:java'
-        src/myLib2/java
+        srcDir: src/myLib2/java
     JVM resources 'myLib2:resources'
-        src/myLib2/resources
+        srcDir: src/myLib2/resources
 
 Binaries
     Jar 'myLib2Jar' (not buildable)
diff --git a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ModelReuseIntegrationTest.groovy b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ModelReuseIntegrationTest.groovy
new file mode 100644
index 0000000..e5b2501
--- /dev/null
+++ b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/ModelReuseIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.jvm
+
+import org.gradle.integtests.fixtures.EnableModelDsl
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
+import org.gradle.model.internal.persist.ReusingModelRegistryStore
+
+// Requires daemon because reuse right now doesn't handle the build actually changing
+class ModelReuseIntegrationTest extends DaemonIntegrationSpec {
+
+    def setup() {
+        EnableModelDsl.enable(executer)
+
+        executer.beforeExecute {
+            withArgument("-D$ReusingModelRegistryStore.TOGGLE=true")
+        }
+    }
+
+    def "can enable reuse with the component model"() {
+        when:
+        buildScript """
+            plugins {
+              id "org.gradle.jvm-component"
+              id "org.gradle.java-lang"
+            }
+
+            model {
+                components {
+                    main(JvmLibrarySpec)
+                }
+            }
+        """
+
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        then:
+        succeeds "build"
+        executedAndNotSkipped ":compileMainJarMainJava"
+
+        when:
+        file("src/main/java/Thing.java").text = "class Thing { static int foo = 1; }"
+
+        then:
+        succeeds "build"
+        executedAndNotSkipped ":compileMainJarMainJava"
+    }
+}
diff --git a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/plugins/JvmComponentPluginIntegrationTest.groovy b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/plugins/JvmComponentPluginIntegrationTest.groovy
index 876202c..f72c0de 100644
--- a/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/plugins/JvmComponentPluginIntegrationTest.groovy
+++ b/subprojects/platform-jvm/src/integTest/groovy/org/gradle/jvm/plugins/JvmComponentPluginIntegrationTest.groovy
@@ -16,19 +16,31 @@
 
 package org.gradle.jvm.plugins
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
 import org.gradle.test.fixtures.archive.JarTestFixture
 import spock.lang.Ignore
 
 class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        EnableModelDsl.enable(executer)
+    }
+
     def "does not create library or binaries when not configured"() {
         when:
         buildFile << """
     plugins {
         id 'jvm-component'
     }
-    task validate << {
-        assert componentSpecs.empty
-        assert binaries.empty
+    model {
+        tasks {
+            create("validate") {
+                doLast {
+                    assert \$("components").size() == 0
+                    assert project.binaries.empty
+                }
+            }
+        }
     }
 """
         then:
@@ -49,36 +61,40 @@ class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
         components {
             myLib(JvmLibrarySpec)
         }
-    }
-
-    task validate << {
-        assert componentSpecs.size() == 1
-        def myLib = componentSpecs.myLib
-        assert myLib.name == 'myLib'
-        assert myLib == componentSpecs['myLib']
-        assert myLib instanceof JvmLibrarySpec
-
-        assert myLib.sources.size() == 0
-
-        assert binaries.size() == 1
-        assert myLib.binaries as Set == binaries as Set
-
-        def myLibJar = (binaries as List)[0]
-        assert myLibJar instanceof JarBinarySpec
-        assert myLibJar.name == 'myLibJar'
-        assert myLibJar.displayName == "Jar 'myLibJar'"
-
-        def binaryTask = tasks['myLibJar']
-        assert binaryTask.group == 'build'
-        assert binaryTask.description == "Assembles Jar 'myLibJar'."
-        assert myLibJar.buildTask == binaryTask
-
-        def jarTask = tasks['createMyLibJar']
-        assert jarTask instanceof org.gradle.jvm.tasks.Jar
-        assert jarTask.group == null
-        assert jarTask.description == "Creates the binary file for Jar 'myLibJar'."
+        tasks {
+            create("validate") {
+            def components = \$("components")
+                doLast {
+                    assert components.size() == 1
+                    def myLib = components.myLib
+                    assert myLib.name == 'myLib'
+                    assert myLib instanceof JvmLibrarySpec
+
+                    assert myLib.sources.size() == 0
+
+                    assert project.binaries.size() == 1
+                    assert myLib.binaries.values() as Set == project.binaries as Set
+
+                    def myLibJar = (project.binaries as List)[0]
+                    assert myLibJar instanceof JarBinarySpec
+                    assert myLibJar.name == 'myLibJar'
+                    assert myLibJar.displayName == "Jar 'myLibJar'"
+
+                    def binaryTask = project.tasks['myLibJar']
+                    assert binaryTask.group == 'build'
+                    assert binaryTask.description == "Assembles Jar 'myLibJar'."
+                    assert myLibJar.buildTask == binaryTask
+
+                    def jarTask = project.tasks['createMyLibJar']
+                    assert jarTask instanceof org.gradle.jvm.tasks.Jar
+                    assert jarTask.group == null
+                    assert jarTask.description == "Creates the binary file for Jar 'myLibJar'."
+                }
+            }
+        }
     }
 """
+
         then:
         succeeds "validate"
     }
@@ -109,7 +125,7 @@ class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
 
     def "can configure jvm binary"() {
         given:
-        buildFile << """
+        buildFile << '''
     plugins {
         id 'jvm-component'
     }
@@ -120,11 +136,11 @@ class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
         }
         jvm {
             allBinaries { jar ->
-                jar.jarFile = file("\${project.buildDir}/bin/\${jar.name}.bin")
+                jar.jarFile = new File($("buildDir"), "bin/${jar.name}.bin")
             }
         }
     }
-"""
+'''
         when:
         succeeds "myJvmLibJar"
 
@@ -200,18 +216,23 @@ class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
             myLibOne(JvmLibrarySpec)
             myLibTwo(JvmLibrarySpec)
         }
-    }
-
-    task validate << {
-        assert componentSpecs.size() == 2
-        assert componentSpecs.myLibOne instanceof JvmLibrarySpec
-        assert componentSpecs.myLibTwo instanceof JvmLibrarySpec
-
-        assert binaries.size() == 2
-        assert binaries.myLibOneJar == componentSpecs.myLibOne.binaries[0]
-        assert binaries.myLibTwoJar == componentSpecs.myLibTwo.binaries[0]
+        tasks {
+            create("validate") {
+                def components = \$("components")
+                doLast {
+                    assert components.size() == 2
+                    assert components.myLibOne instanceof JvmLibrarySpec
+                    assert components.myLibTwo instanceof JvmLibrarySpec
+
+                    assert project.binaries.size() == 2
+                    assert project.binaries.myLibOneJar == components.myLibOne.binaries.values()[0]
+                    assert project.binaries.myLibTwoJar == components.myLibTwo.binaries.values()[0]
+                }
+            }
+        }
     }
 """
+
         then:
         succeeds "validate"
     }
@@ -243,4 +264,4 @@ class JvmComponentPluginIntegrationTest extends AbstractIntegrationSpec {
         then:
         executed ":createMyLibOneJar", ":myLibOneJar", ":createMyLibTwoJar", ":myLibTwoJar"
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/plugins/JvmComponentPlugin.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/plugins/JvmComponentPlugin.java
index ae66265..32eccb5 100644
--- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/plugins/JvmComponentPlugin.java
+++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/plugins/JvmComponentPlugin.java
@@ -30,11 +30,7 @@ import org.gradle.jvm.platform.internal.DefaultJavaPlatform;
 import org.gradle.jvm.tasks.Jar;
 import org.gradle.jvm.toolchain.JavaToolChainRegistry;
 import org.gradle.jvm.toolchain.internal.DefaultJavaToolChainRegistry;
-import org.gradle.model.Model;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.*;
 import org.gradle.platform.base.*;
 import org.gradle.platform.base.internal.*;
 import org.gradle.platform.base.internal.toolchain.ToolResolver;
@@ -46,7 +42,7 @@ import java.util.List;
 
 /**
  * Base plugin for JVM component support. Applies the {@link org.gradle.language.base.plugins.ComponentModelBasePlugin}. Registers the {@link org.gradle.jvm.JvmLibrarySpec} library type for
- * the {@link org.gradle.platform.base.ComponentSpecContainer}.
+ * the components container.
  */
 @Incubating
 @SuppressWarnings("UnusedDeclaration")
@@ -84,7 +80,7 @@ public class JvmComponentPlugin extends RuleSource {
     }
 
     @ComponentBinaries
-    public void createBinaries(CollectionBuilder<JarBinarySpec> binaries, final JvmLibrarySpec jvmLibrary,
+    public void createBinaries(ModelMap<JarBinarySpec> binaries, final JvmLibrarySpec jvmLibrary,
                                PlatformResolvers platforms, BinaryNamingSchemeBuilder namingSchemeBuilder, final JvmComponentExtension jvmComponentExtension,
                                @Path("buildDir") File buildDir, ServiceRegistry serviceRegistry, JavaToolChainRegistry toolChains) {
 
@@ -116,7 +112,7 @@ public class JvmComponentPlugin extends RuleSource {
     }
 
     @BinaryTasks
-    public void createTasks(CollectionBuilder<Task> tasks, final JarBinarySpec binary) {
+    public void createTasks(ModelMap<Task> tasks, final JarBinarySpec binary) {
         String taskName = "create" + StringUtils.capitalize(binary.getName());
         tasks.create(taskName, Jar.class, new Action<Jar>() {
             @Override
diff --git a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/tasks/Jar.java b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/tasks/Jar.java
index ea4e6d2..5a31d20 100644
--- a/subprojects/platform-jvm/src/main/java/org/gradle/jvm/tasks/Jar.java
+++ b/subprojects/platform-jvm/src/main/java/org/gradle/jvm/tasks/Jar.java
@@ -54,7 +54,7 @@ public class Jar extends Zip {
         metaInf = (CopySpecInternal) getRootSpec().addFirst().into("META-INF");
         metaInf.addChild().from(new Callable<FileTreeAdapter>() {
             public FileTreeAdapter call() throws Exception {
-                MapFileTree manifestSource = new MapFileTree(getTemporaryDirFactory(), getFileSystem());
+                MapFileTree manifestSource = new MapFileTree(getTemporaryDirFactory(), getFileSystem(), MapFileTree.FileCreationMode.KEEP_EXISTING);
                 manifestSource.add("MANIFEST.MF", new Action<OutputStream>() {
                     public void execute(OutputStream outputStream) {
                         Manifest manifest = getManifest();
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
index 80fdcc8..2927680 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
@@ -163,10 +163,10 @@ class DefaultManifestTest extends Specification {
         TestFile manifestFile = tmpDir.file('someNonexistingDir').file('someFile')
         DefaultManifest manifest = new DefaultManifest(fileResolver).attributes(key1: 'value1')
         fileResolver.resolve('file') >> manifestFile
-        
+
         when:
         manifest.writeTo('file')
-        Manifest fileManifest = new Manifest(new FileReader(manifestFile))
+        Manifest fileManifest = manifestFile.withReader { new Manifest(it) }
         Manifest expectedManifest = new Manifest()
         expectedManifest.addConfiguredAttribute(new Attribute('key1', 'value1'))
         expectedManifest.addConfiguredAttribute(new Attribute('Manifest-Version', '1.0'))
@@ -174,4 +174,4 @@ class DefaultManifestTest extends Specification {
         then:
         fileManifest.equals(expectedManifest)
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/DefaultJvmLibrarySpecTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/DefaultJvmLibrarySpecTest.groovy
index 347c2cb..836d7ad 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/DefaultJvmLibrarySpecTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/DefaultJvmLibrarySpecTest.groovy
@@ -15,17 +15,19 @@
  */
 
 package org.gradle.jvm.internal
+
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.base.LanguageSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.platform.base.ComponentSpecIdentifier
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.platform.base.component.BaseComponentFixtures
+import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
 class DefaultJvmLibrarySpecTest extends Specification {
-    def libraryId = Mock(ComponentSpecIdentifier)
+    def libraryId = new DefaultComponentSpecIdentifier(":project-path", "jvm-lib")
     FunctionalSourceSet mainSourceSet
 
     def setup(){
@@ -33,11 +35,8 @@ class DefaultJvmLibrarySpecTest extends Specification {
     }
 
     def "library has name and path"() {
-        def library = createJvmLibrarySpec()
-
         when:
-        _ * libraryId.name >> "jvm-lib"
-        _ * libraryId.projectPath >> ":project-path"
+        def library = createJvmLibrarySpec()
 
         then:
         library.name == "jvm-lib"
@@ -56,11 +55,11 @@ class DefaultJvmLibrarySpecTest extends Specification {
         mainSourceSet.add(lss2)
 
         then:
-        library.getSource() as List == [lss1, lss2]
+        library.getSource().values() as List == [lss1, lss2]
     }
 
     private DefaultJvmLibrarySpec createJvmLibrarySpec() {
-        BaseComponentSpec.create(DefaultJvmLibrarySpec, libraryId, mainSourceSet, DirectInstantiator.INSTANCE)
+        BaseComponentFixtures.create(DefaultJvmLibrarySpec, new ModelRegistryHelper(), libraryId, mainSourceSet, DirectInstantiator.INSTANCE)
     }
 
     def languageSourceSet(String name) {
diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/plugins/CreateJvmBinariesTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/plugins/CreateJvmBinariesTest.groovy
index 9a91c15..329d8bc 100644
--- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/plugins/CreateJvmBinariesTest.groovy
+++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/internal/plugins/CreateJvmBinariesTest.groovy
@@ -15,6 +15,7 @@
  */
 
 package org.gradle.jvm.internal.plugins
+
 import org.gradle.api.Action
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
@@ -30,9 +31,11 @@ import org.gradle.jvm.toolchain.JavaToolChainRegistry
 import org.gradle.language.base.LanguageSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
+import org.gradle.model.internal.core.MutableModelNode
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.platform.base.ComponentSpecIdentifier
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.BinaryNamingScheme
 import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder
 import org.gradle.platform.base.internal.PlatformResolvers
@@ -45,11 +48,14 @@ class CreateJvmBinariesTest extends Specification {
     def toolChain = Mock(JavaToolChainInternal)
     def rule = new JvmComponentPlugin()
     def platforms = Mock(PlatformResolvers)
-    CollectionBuilder<JarBinarySpec> binaries = Mock(CollectionBuilder)
+    ModelMap<JarBinarySpec> binaries = Mock(ModelMap)
     def instantiator = Mock(Instantiator)
     def mainSourceSet = new DefaultFunctionalSourceSet("ss", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet))
     def toolChainRegistry = Mock(JavaToolChainRegistry)
     def toolResolver = Mock(ToolResolver)
+    def binariesNode = Mock(MutableModelNode) {
+
+    }
 
     def serviceRegistry = ServiceRegistryBuilder.builder().provider(new Object() {
         Instantiator createInstantiator() {
@@ -61,7 +67,7 @@ class CreateJvmBinariesTest extends Specification {
     }).build()
 
     def "adds a binary for each jvm library"() {
-        def library = BaseComponentSpec.create(DefaultJvmLibrarySpec, componentId("jvmLibOne", ":project-path"), mainSourceSet, DirectInstantiator.INSTANCE)
+        def library = BaseComponentFixtures.create(DefaultJvmLibrarySpec, new ModelRegistryHelper(), componentId("jvmLibOne", ":project-path"), mainSourceSet, DirectInstantiator.INSTANCE)
         def namingScheme = Mock(BinaryNamingScheme)
         def jvmExtension = Mock(JvmComponentExtension)
         def platform = new DefaultJavaPlatform("test")
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryBuildTypesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryBuildTypesIntegrationTest.groovy
index a5a77cd..30aab52 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryBuildTypesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryBuildTypesIntegrationTest.groovy
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 package org.gradle.nativeplatform
+
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
+ at LeaksFileHandles
 class BinaryBuildTypesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def helloWorldApp = new CppHelloWorldApp()
 
@@ -174,7 +177,7 @@ model {
         fails "mainExecutable"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.plugins.NativeComponentModelPlugin\$Rules#createNativeBinaries(")
+        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.internal.configure.NativeComponentRules#createBinaries(")
         failure.assertHasCause("Invalid BuildType: 'unknown'")
     }
 
@@ -205,6 +208,6 @@ model {
         fails "releaseMainExecutable"
 
         then:
-        failure.assertHasDescription("No static library binary available for library 'hello' with [flavor: 'default', platform: '${NativePlatforms.defaultPlatformName}', buildType: 'release']")
+        failure.assertHasDescription("No static library binary available for library 'hello' with [flavor: 'default', platform: '${NativePlatformsTestFixture.defaultPlatformName}', buildType: 'release']")
     }
 }
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryConfigurationIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryConfigurationIntegrationTest.groovy
index a954462..603bbf7 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryConfigurationIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryConfigurationIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.nativeplatform
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -25,6 +26,7 @@ import spock.lang.IgnoreIf
 import spock.lang.Issue
 import spock.lang.Unroll
 
+ at LeaksFileHandles
 class BinaryConfigurationIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "can configure the binaries of a C++ application"() {
         given:
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryFlavorsIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryFlavorsIntegrationTest.groovy
index 0f32a9c..706b875 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryFlavorsIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/BinaryFlavorsIntegrationTest.groovy
@@ -16,13 +16,15 @@
 package org.gradle.nativeplatform
 
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
 import org.gradle.nativeplatform.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.HelloWorldApp
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class BinaryFlavorsIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     static final DEFAULT = HelloWorldApp.HELLO_WORLD
     static final FRENCH = HelloWorldApp.HELLO_WORLD_FRENCH
@@ -152,7 +154,7 @@ model {
 
         then:
         fails "germanMainExecutable"
-        failure.assertHasDescription("No shared library binary available for library 'hello' with [flavor: 'german', platform: '${NativePlatforms.defaultPlatformName}', buildType: 'debug']")
+        failure.assertHasDescription("No shared library binary available for library 'hello' with [flavor: 'german', platform: '${NativePlatformsTestFixture.defaultPlatformName}', buildType: 'debug']")
     }
 
     def "fails with reasonable error message when trying to target an unknown flavor"() {
@@ -169,7 +171,7 @@ model {
         fails "mainExecutable"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.plugins.NativeComponentModelPlugin\$Rules#createNativeBinaries")
+        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.internal.configure.NativeComponentRules#createBinaries(")
         failure.assertHasCause("Invalid Flavor: 'unknown'")
     }
 }
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ComponentReportIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ComponentReportIntegrationTest.groovy
index 43d7dd0..1ce71d3 100644
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ComponentReportIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ComponentReportIntegrationTest.groovy
@@ -15,12 +15,14 @@
  */
 package org.gradle.nativeplatform
 
-import org.gradle.api.reporting.components.AbstractComponentReportIntegrationTest
+import org.gradle.api.reporting.components.NativeComponentReportIntegrationTest
 import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 
-class ComponentReportIntegrationTest extends AbstractComponentReportIntegrationTest {
+class ComponentReportIntegrationTest extends NativeComponentReportIntegrationTest {
     private String currentNative = NativePlatformsTestFixture.defaultPlatformName
 
+    @RequiresInstalledToolChain
     def "shows details of native C++ library"() {
         given:
         buildFile << """
@@ -47,7 +49,7 @@ Native library 'someLib'
 
 Source sets
     C++ source 'someLib:cpp'
-        src/someLib/cpp
+        srcDir: src/someLib/cpp
 
 Binaries
     Shared library 'someLib:sharedLibrary'
@@ -67,6 +69,7 @@ Binaries
 """
     }
 
+    @RequiresInstalledToolChain
     def "shows details of native C++ library that is not buildable"() {
         given:
         buildFile << """
@@ -101,7 +104,7 @@ Native library 'anotherLib'
 
 Source sets
     C++ source 'anotherLib:cpp'
-        src/anotherLib/cpp
+        srcDir: src/anotherLib/cpp
 
 Binaries
     Shared library 'anotherLib:sharedLibrary'
@@ -125,7 +128,7 @@ Native library 'someLib'
 
 Source sets
     C++ source 'someLib:cpp'
-        src/someLib/cpp
+        srcDir: src/someLib/cpp
 
 Binaries
     Shared library 'someLib:sharedLibrary' (not buildable)
@@ -149,6 +152,7 @@ Binaries
 """
     }
 
+    @RequiresInstalledToolChain
     def "shows details of polyglot native library with multiple variants"() {
         given:
         buildFile << """
@@ -188,11 +192,11 @@ Native library 'someLib'
 
 Source sets
     Assembler source 'someLib:asm'
-        src/someLib/asm
+        srcDir: src/someLib/asm
     C source 'someLib:c'
-        src/someLib/c
+        srcDir: src/someLib/c
     C++ source 'someLib:cpp'
-        src/someLib/cpp
+        srcDir: src/someLib/cpp
 
 Binaries
     Shared library 'someLib:amd64:free:sharedLibrary'
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryApiDependenciesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryApiDependenciesIntegrationTest.groovy
index 0625a27..9e18917 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryApiDependenciesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryApiDependenciesIntegrationTest.groovy
@@ -17,11 +17,13 @@ package org.gradle.nativeplatform
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Unroll
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class LibraryApiDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "setup"() {
         settingsFile << "rootProject.name = 'test'"
@@ -144,9 +146,7 @@ model {
             binaries.all { binary ->
                 sources {
                     buildTypeSources(CppSourceSet) {
-                        sources {
-                            exportedHeaders.srcDir "src/util/\${binary.buildType.name}"
-                        }
+                        exportedHeaders.srcDir "src/util/\${binary.buildType.name}"
                     }
                 }
             }
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryBinariesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryBinariesIntegrationTest.groovy
index c356b2a..60046ff 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryBinariesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryBinariesIntegrationTest.groovy
@@ -16,11 +16,13 @@
 package org.gradle.nativeplatform
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Issue
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class LibraryBinariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "setup"() {
         settingsFile << "rootProject.name = 'test'"
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryDependenciesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryDependenciesIntegrationTest.groovy
index a2d15ce..4e1ad55 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryDependenciesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/LibraryDependenciesIntegrationTest.groovy
@@ -19,11 +19,13 @@ import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationS
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.ExeWithDiamondDependencyHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.ExeWithLibraryUsingLibraryHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Unroll
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class LibraryDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "setup"() {
         settingsFile << "rootProject.name = 'test'"
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ModelReuseIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ModelReuseIntegrationTest.groovy
new file mode 100644
index 0000000..e4bf331
--- /dev/null
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/ModelReuseIntegrationTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform
+
+import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec
+import org.gradle.model.internal.persist.ReusingModelRegistryStore
+import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
+import org.gradle.nativeplatform.fixtures.SingleToolChainTestRunner
+import org.junit.runner.RunWith
+
+// Requires daemon because reuse right now doesn't handle the build actually changing
+ at RequiresInstalledToolChain
+ at RunWith(SingleToolChainTestRunner.class)
+class ModelReuseIntegrationTest extends DaemonIntegrationSpec {
+
+    def setup() {
+        def toolChain = AbstractInstalledToolChainIntegrationSpec.toolChain
+        def initScript = file("init.gradle") << """
+allprojects { p ->
+    apply plugin: ${toolChain.pluginClass}
+
+    model {
+          toolChains {
+            ${toolChain.buildScriptConfig}
+          }
+    }
+}
+"""
+        executer.beforeExecute {
+            usingInitScript(initScript)
+            withArgument("-D$ReusingModelRegistryStore.TOGGLE=true")
+        }
+    }
+
+    def "can enable reuse with the component model"() {
+        when:
+        buildScript """
+            plugins {
+              id "c"
+            }
+
+            model {
+                components {
+                  main(NativeExecutableSpec)
+                }
+            }
+        """
+
+        file("src/main/c/lib.c") << """
+            int main() {
+              return 0;
+            }
+        """
+
+        then:
+        succeeds "build"
+        executedAndNotSkipped ":compileMainExecutableMainC"
+
+        when:
+        file("src/main/c/lib.c").text = """
+            int main() {
+              return 10;
+            }
+        """
+
+        then:
+        succeeds "build"
+        executedAndNotSkipped ":compileMainExecutableMainC"
+    }
+}
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativeBinariesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativeBinariesIntegrationTest.groovy
index a5ac95c..c09b2b3 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativeBinariesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativeBinariesIntegrationTest.groovy
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 package org.gradle.nativeplatform
+
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.CppCallingCHelloWorldApp
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.hamcrest.Matchers
 
 import static org.gradle.util.Matchers.containsText
 
+ at LeaksFileHandles
 class NativeBinariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def helloWorldApp = new CppCallingCHelloWorldApp()
 
@@ -70,7 +73,7 @@ model {
     components {
         main(NativeExecutableSpec) {
             targetPlatform "unknown"
-            targetPlatform NativePlatforms.defaultPlatformName
+            targetPlatform "${NativePlatformsTestFixture.defaultPlatformName}"
         }
     }
 }
@@ -79,11 +82,11 @@ model {
         succeeds "assemble"
 
         then:
-        executedAndNotSkipped ":${NativePlatforms.defaultPlatformName}MainExecutable"
+        executedAndNotSkipped ":${NativePlatformsTestFixture.defaultPlatformName}MainExecutable"
         notExecuted ":unknownMainExecutable"
 
         and:
-        executable("build/binaries/mainExecutable/${NativePlatforms.defaultPlatformName}/main").assertExists()
+        executable("build/binaries/mainExecutable/${NativePlatformsTestFixture.defaultPlatformName}/main").assertExists()
         executable("build/binaries/mainExecutable/unknown/main").assertDoesNotExist()
     }
 
@@ -226,7 +229,7 @@ model {
 
         then:
         fails "mainExecutable"
-        failure.assertHasCause("Exception thrown while executing model rule: model.components > create(main)");
+        failure.assertHasCause("Exception thrown while executing model rule: model.components > main.<init> > components.main.getSources() > create(java)");
         failure.assertHasCause("Cannot create a JavaSourceSet because this type is not known to this container. Known types are: CSourceSet, CppSourceSet")
     }
 
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy
index 4ceb6a5..a51dc64 100644
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.nativeplatform
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
@@ -27,6 +28,7 @@ import org.junit.Rule
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.GccCompatible
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class NativePlatformSamplesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     @Rule final TestNameTestDirectoryProvider testDirProvider = new TestNameTestDirectoryProvider()
     @Rule public final Sample cppLib = sample(testDirProvider, 'cpp-lib')
@@ -237,4 +239,4 @@ Util build type: DEBUG
 Util build type: RELEASE
 """
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/PrebuiltLibrariesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/PrebuiltLibrariesIntegrationTest.groovy
index 560e3c7..e5312d8 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/PrebuiltLibrariesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/PrebuiltLibrariesIntegrationTest.groovy
@@ -16,10 +16,12 @@
 package org.gradle.nativeplatform
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class PrebuiltLibrariesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     final app = new CppHelloWorldApp()
 
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteDefinitionIntegrationSpec.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteDefinitionIntegrationSpec.groovy
new file mode 100644
index 0000000..1f61ae3
--- /dev/null
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteDefinitionIntegrationSpec.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+
+class TestSuiteDefinitionIntegrationSpec extends AbstractIntegrationSpec {
+    def setup() {
+        buildFile << """
+interface CustomTestSuite extends TestSuiteSpec {
+}
+
+class DefaultCustomTestSuite extends BaseComponentSpec implements CustomTestSuite {
+    ComponentSpec testedComponent
+}
+
+interface CustomTestBinary extends TestSuiteBinarySpec {
+    String getData()
+}
+
+class DefaultCustomTestBinary extends BaseBinarySpec implements CustomTestBinary {
+    TestSuiteSpec testSuite
+    String data
+}
+
+interface CustomTestSourceSet extends LanguageSourceSet {
+}
+
+class DefaultCustomTestSourceSet extends BaseLanguageSourceSet implements CustomTestSourceSet {
+}
+
+class TestSuitePlugin extends RuleSource {
+    @ComponentType
+    void registerTestSuiteType(ComponentTypeBuilder<CustomTestSuite> builder) {
+        builder.defaultImplementation(DefaultCustomTestSuite)
+    }
+
+    @BinaryType
+    void registerBinaryType(BinaryTypeBuilder<CustomTestBinary> builder) {
+        builder.defaultImplementation(DefaultCustomTestBinary)
+    }
+
+    @LanguageType
+    void registerLanguageType(LanguageTypeBuilder<CustomTestSourceSet> builder) {
+        builder.defaultImplementation(DefaultCustomTestSourceSet)
+    }
+
+    @Mutate
+    void testSuiteDefaults(TestSuiteContainer testSuites) {
+//        testSuites.withType(CustomTestSuite).beforeEach { suite ->
+        testSuites.withType(CustomTestSuite) { suite ->
+            suite.sources.create('tests', CustomTestSourceSet)
+            suite.binaries.create('tests', CustomTestBinary)
+        }
+    }
+}
+"""
+    }
+
+    def "plugin can define custom test suite and attach source sets and binaries"() {
+        buildFile << """
+
+apply plugin: NativeBinariesTestPlugin
+apply plugin: TestSuitePlugin
+
+model {
+    testSuites {
+        unitTests(CustomTestSuite)
+    }
+}
+"""
+
+        when:
+        run "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    testSuites
+        unitTests
+            binaries
+                tests
+                    tasks = []
+            sources
+                tests = DefaultCustomTestSourceSet 'unitTests:tests'
+"""))
+
+        when:
+        run "check"
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "plugin can define a test suite as an output of the project"() {
+        buildFile << """
+
+apply plugin: NativeBinariesTestPlugin
+apply plugin: TestSuitePlugin
+
+model {
+    components {
+        unitTests(CustomTestSuite)
+    }
+}
+"""
+
+        when:
+        run "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    components
+        unitTests
+            binaries
+            sources"""))
+
+        when:
+        run "assemble"
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteModelIntegrationSpec.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteModelIntegrationSpec.groovy
new file mode 100644
index 0000000..8b439e1
--- /dev/null
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/TestSuiteModelIntegrationSpec.groovy
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.EnableModelDsl
+import org.gradle.util.TextUtil
+
+class TestSuiteModelIntegrationSpec extends AbstractIntegrationSpec {
+
+    def "setup"() {
+        EnableModelDsl.enable(executer)
+
+        buildScript """
+            apply type: NativeBinariesTestPlugin
+
+            interface CustomTestSuite extends TestSuiteSpec {}
+            class DefaultCustomTestSuite extends BaseComponentSpec implements CustomTestSuite {
+                ComponentSpec testedComponent
+            }
+
+            interface CustomLanguageSourceSet extends LanguageSourceSet {
+                String getData();
+            }
+            class DefaultCustomLanguageSourceSet extends BaseLanguageSourceSet implements CustomLanguageSourceSet {
+                final String data = "foo"
+            }
+
+            class TestSuiteTypeRules extends RuleSource {
+                @ComponentType
+                void registerCustomTestSuiteType(ComponentTypeBuilder<CustomTestSuite> builder) {
+                    builder.defaultImplementation(DefaultCustomTestSuite)
+                }
+
+                @LanguageType
+                void registerCustomLanguageType(LanguageTypeBuilder<CustomLanguageSourceSet> builder) {
+                    builder.defaultImplementation(DefaultCustomLanguageSourceSet)
+                }
+            }
+
+            apply type: TestSuiteTypeRules
+
+            model {
+                testSuites {
+                    main(CustomTestSuite)
+                }
+            }
+        """
+    }
+
+    void withMainSourceSet() {
+        buildFile << """
+            model {
+                testSuites {
+                    main {
+                        sources {
+                            main(CustomLanguageSourceSet)
+                        }
+                    }
+                }
+            }
+        """
+    }
+
+    void withTestBinaryFactory() {
+        buildFile << """
+            import org.gradle.api.internal.project.taskfactory.ITaskFactory
+
+            interface CustomTestBinary extends TestSuiteBinarySpec {
+                String getData()
+            }
+
+            class DefaultCustomTestBinary extends BaseBinarySpec implements CustomTestBinary {
+                TestSuiteSpec testSuite
+                String data = "foo"
+            }
+
+            class TestBinaryTypeRules extends RuleSource {
+                @BinaryType
+                public void registerCustomTestBinaryFactory(BinaryTypeBuilder<CustomTestBinary> builder) {
+                    builder.defaultImplementation(DefaultCustomTestBinary)
+                }
+            }
+
+            apply type: TestBinaryTypeRules
+        """
+    }
+
+    def "test suite sources and binaries containers are visible in model report"() {
+        when:
+        run "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    testSuites
+        main
+            binaries
+            sources"""))
+    }
+
+    def "can reference sources container for a test suite in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printSourceNames") {
+                        def sources = $("testSuites.main.sources")
+                        doLast {
+                            println "names: ${sources.values()*.name}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printSourceNames"
+
+        then:
+        output.contains "names: [main]"
+    }
+
+    def "test suite sources container elements are visible in model report"() {
+        given:
+        withMainSourceSet()
+        buildFile << """
+            model {
+                testSuites {
+                    main {
+                        sources {
+                            test(CustomLanguageSourceSet)
+                        }
+                    }
+                    secondary(CustomTestSuite) {
+                        sources {
+                            test(CustomLanguageSourceSet)
+                        }
+                    }
+                    foo(CustomTestSuite) {
+                        sources {
+                            bar(CustomLanguageSourceSet)
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        run "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    testSuites
+        foo
+            binaries
+            sources
+                bar = DefaultCustomLanguageSourceSet 'foo:bar'
+        main
+            binaries
+            sources
+                main = DefaultCustomLanguageSourceSet 'main:main'
+                test = DefaultCustomLanguageSourceSet 'main:test'
+        secondary
+            binaries
+            sources
+                test = DefaultCustomLanguageSourceSet 'secondary:test'"""))
+    }
+
+    def "can reference sources container elements in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            model {
+                tasks {
+                    create("printSourceDisplayName") {
+                        def sources = $("testSuites.main.sources.main")
+                        doLast {
+                            println "sources display name: ${sources.displayName}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printSourceDisplayName"
+
+        then:
+        output.contains "sources display name: DefaultCustomLanguageSourceSet 'main:main'"
+    }
+
+    def "can reference sources container elements using specialized type in a rule"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            class TaskRules extends RuleSource {
+                @Mutate
+                void addPrintSourceDisplayNameTask(ModelMap<Task> tasks, @Path("testSuites.main.sources.main") CustomLanguageSourceSet sourceSet) {
+                    tasks.create("printSourceData") {
+                        doLast {
+                            println "sources data: ${sourceSet.data}"
+                        }
+                    }
+                }
+            }
+
+            apply type: TaskRules
+        '''
+
+        when:
+        succeeds "printSourceData"
+
+        then:
+        output.contains "sources data: foo"
+    }
+
+    def "cannot remove source sets"() {
+        given:
+        withMainSourceSet()
+        buildFile << '''
+            class SourceSetRemovalRules extends RuleSource {
+                @Mutate
+                void clearSourceSets(@Path("testSuites.main.sources") NamedDomainObjectCollection<LanguageSourceSet> sourceSets) {
+                    sourceSets.clear()
+                }
+
+                @Mutate
+                void closeMainComponentSourceSetsForTasks(ModelMap<Task> tasks, @Path("testSuites.main.sources") NamedDomainObjectCollection<LanguageSourceSet> sourceSets) {
+                }
+            }
+
+            apply type: SourceSetRemovalRules
+        '''
+
+        when:
+        fails()
+
+        then:
+        failureHasCause("This collection does not support element removal.")
+    }
+
+    def "test suite binaries container elements and their tasks containers are visible in model report"() {
+        given:
+        withTestBinaryFactory()
+        buildFile << '''
+            model {
+                testSuites {
+                    main {
+                        binaries {
+                            first(CustomTestBinary)
+                            second(CustomTestBinary)
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        run "model"
+
+        then:
+        output.contains(TextUtil.toPlatformLineSeparators("""
+    testSuites
+        main
+            binaries
+                first
+                    tasks = []
+                second
+                    tasks = []"""))
+    }
+
+    def "can reference binaries container for a test suite in a rule"() {
+        given:
+        withTestBinaryFactory()
+        buildFile << '''
+            model {
+                testSuites {
+                    main {
+                        binaries {
+                            first(CustomTestBinary)
+                            second(CustomTestBinary)
+                        }
+                    }
+                }
+                tasks {
+                    create("printBinaryNames") {
+                        def binaries = $("testSuites.main.binaries")
+                        doLast {
+                            println "names: ${binaries.values().name}"
+                        }
+                    }
+                }
+            }
+        '''
+
+        when:
+        succeeds "printBinaryNames"
+
+        then:
+        output.contains "names: [first, second]"
+    }
+
+    def "can reference binaries container elements using specialized type in a rule"() {
+        given:
+        withTestBinaryFactory()
+        buildFile << '''
+            model {
+                testSuites {
+                    main {
+                        binaries {
+                            main(CustomTestBinary)
+                        }
+                    }
+                }
+            }
+            class TaskRules extends RuleSource {
+                @Mutate
+                void addPrintSourceDisplayNameTask(ModelMap<Task> tasks, @Path("testSuites.main.binaries.main") CustomTestBinary binary) {
+                    tasks.create("printBinaryData") {
+                        doLast {
+                            println "binary data: ${binary.data}"
+                        }
+                    }
+                }
+            }
+
+            apply type: TaskRules
+        '''
+
+        when:
+        succeeds "printBinaryData"
+
+        then:
+        output.contains "binary data: foo"
+    }
+
+}
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy
index 1c28950..1d175dc 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy
@@ -26,11 +26,13 @@ import org.gradle.nativeplatform.fixtures.binaryinfo.DumpbinBinaryInfo
 import org.gradle.nativeplatform.fixtures.binaryinfo.OtoolBinaryInfo
 import org.gradle.nativeplatform.fixtures.binaryinfo.ReadelfBinaryInfo
 import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.Unroll
 
 @Requires(TestPrecondition.NOT_UNKNOWN_OS)
+ at LeaksFileHandles
 class BinaryNativePlatformIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def testApp = new PlatformDetectingTestApp()
     def os = OperatingSystem.current()
@@ -368,7 +370,7 @@ model {
         fails "mainExecutable"
 
         then:
-        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.plugins.NativeComponentModelPlugin\$Rules#createNativeBinaries")
+        failure.assertHasCause("Exception thrown while executing model rule: org.gradle.nativeplatform.internal.configure.NativeComponentRules#createBinaries(")
         failure.assertHasCause("Invalid NativePlatform: unknown")
     }
 
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy
index d72314c..e163f37 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy
@@ -23,9 +23,11 @@ import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.MixedLanguageHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.WindowsResourceHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VisualCpp
 // TODO:DAZ Test incremental
+ at LeaksFileHandles
 class GeneratedSourcesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def setup() {
@@ -198,7 +200,9 @@ model {
     components {
         main(NativeExecutableSpec) {
             sources {
-                c.lib library: 'hello', linkage: 'api'
+                c {
+                lib library: 'hello', linkage: 'api'
+                }
             }
         }
         hello(NativeLibrarySpec) {
@@ -445,7 +449,7 @@ model {
                 "build/src/generated/c/main.c",
                 "build/src/generated/c/sum.c"
         ] as Set
-        projectFile.headerFiles == [ "build/src/generated/headers/hello.h" ]
+        projectFile.headerFiles.sort() == [ "build/src/generated/headers/common.h", "build/src/generated/headers/hello.h" ]
         projectFile.projectConfigurations.keySet() == ['debug'] as Set
         with (projectFile.projectConfigurations['debug']) {
             // TODO - should not include the default location
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetCompileDependenciesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetCompileDependenciesIntegrationTest.groovy
index 3474070..46c9cba 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetCompileDependenciesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetCompileDependenciesIntegrationTest.groovy
@@ -16,7 +16,9 @@
 package org.gradle.nativeplatform.sourceset
 
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 class SourceSetCompileDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "setup"() {
         settingsFile << "rootProject.name = 'test'"
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetDependenciesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetDependenciesIntegrationTest.groovy
index 94ed005..e84f762 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetDependenciesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetDependenciesIntegrationTest.groovy
@@ -20,11 +20,13 @@ package org.gradle.nativeplatform.sourceset
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
 import org.gradle.nativeplatform.fixtures.app.CppCallingCHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 // 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
+ at LeaksFileHandles
 class SourceSetDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def "source dependency on source set of same type"() {
@@ -43,7 +45,7 @@ model {
                     source.srcDir "src/library/c"
                     exportedHeaders.srcDir "src/library/headers"
                 }
-                c.lib sources.library
+                c.lib library
             }
         }
     }
@@ -100,7 +102,7 @@ model {
                     exportedHeaders.srcDir "src/library/headers"
                     source.srcDir "src/library/c"
                 }
-                cpp.lib sources.library
+                cpp.lib library
             }
         }
     }
@@ -166,4 +168,4 @@ model {
         expect:
         succeeds "mainExecutable"
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetLinkDependenciesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetLinkDependenciesIntegrationTest.groovy
index 5492505..6cb6b77 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetLinkDependenciesIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/SourceSetLinkDependenciesIntegrationTest.groovy
@@ -17,10 +17,12 @@
 
 package org.gradle.nativeplatform.sourceset
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class SourceSetLinkDependenciesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def "setup"() {
         settingsFile << "rootProject.name = 'test'"
@@ -68,7 +70,6 @@ model {
         lib1(NativeLibrarySpec)
         main(NativeExecutableSpec) {
             sources {
-                cpp(CppSourceSet)
                 cpp1(CppSourceSet)
             }
         }
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/CommonToolchainCustomizationIntegTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/CommonToolchainCustomizationIntegTest.groovy
index 660eca8..a00a463 100644
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/CommonToolchainCustomizationIntegTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/CommonToolchainCustomizationIntegTest.groovy
@@ -18,7 +18,9 @@ package org.gradle.nativeplatform.toolchain;
 
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
+ at LeaksFileHandles
 public class CommonToolchainCustomizationIntegTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def helloWorldApp = new CppHelloWorldApp()
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainDiscoveryIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainDiscoveryIntegrationTest.groovy
index d7c39c6..2ca464c 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainDiscoveryIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainDiscoveryIntegrationTest.groovy
@@ -18,9 +18,9 @@ package org.gradle.nativeplatform.toolchain
 
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
 import org.hamcrest.Matchers
 import spock.lang.IgnoreIf
 
@@ -115,7 +115,7 @@ model {
 
         then:
         failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainC'.")
-        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatforms.defaultPlatformName}'"))
+        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatformsTestFixture.defaultPlatformName}'"))
         failure.assertThatCause(Matchers.containsString("- ${toolChain.instanceDisplayName}: Could not find C compiler 'does-not-exist'"))
     }
 
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/MultipleNativeToolChainIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/MultipleNativeToolChainIntegrationTest.groovy
index fc52a9c..cc6dc51 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/MultipleNativeToolChainIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/MultipleNativeToolChainIntegrationTest.groovy
@@ -22,10 +22,12 @@ import org.gradle.nativeplatform.fixtures.AvailableToolChains
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.ToolChainRequirement
 import org.gradle.nativeplatform.fixtures.app.CppCompilerDetectingTestApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @RequiresInstalledToolChain
+ at LeaksFileHandles
 class MultipleNativeToolChainIntegrationTest extends AbstractIntegrationSpec {
     def helloWorld = new CppCompilerDetectingTestApp()
 
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/NativeToolChainDiscoveryIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/NativeToolChainDiscoveryIntegrationTest.groovy
index 6f9dd61..2245f3a 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/NativeToolChainDiscoveryIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/NativeToolChainDiscoveryIntegrationTest.groovy
@@ -16,10 +16,12 @@
 package org.gradle.nativeplatform.toolchain
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.app.CppCompilerDetectingTestApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 /**
  * Test that each available tool chain can be discovered and used without configuration, assuming it is in the path.
  */
+ at LeaksFileHandles
 class NativeToolChainDiscoveryIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def helloWorldApp = new CppCompilerDetectingTestApp()
diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/VisualCppToolChainDiscoveryIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/VisualCppToolChainDiscoveryIntegrationTest.groovy
index 49f46be..0e12459 100755
--- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/VisualCppToolChainDiscoveryIntegrationTest.groovy
+++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/VisualCppToolChainDiscoveryIntegrationTest.groovy
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 package org.gradle.nativeplatform.toolchain
+
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
+import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
 import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 import org.gradle.nativeplatform.fixtures.ToolChainRequirement
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
-import org.gradle.nativeplatform.platform.internal.NativePlatforms
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.hamcrest.Matchers
 
 @RequiresInstalledToolChain(ToolChainRequirement.VisualCpp)
+ at LeaksFileHandles
 class VisualCppToolChainDiscoveryIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
     def helloWorldApp = new CHelloWorldApp()
 
@@ -59,7 +62,7 @@ model {
 
         then:
         failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainC'.")
-        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatforms.defaultPlatformName}'"))
+        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatformsTestFixture.defaultPlatformName}'"))
         failure.assertThatCause(Matchers.containsString("- ${toolChain.instanceDisplayName}: The specified installation directory '${file('does-not-exist')}' does not appear to contain a Visual Studio installation."))
     }
 
@@ -78,7 +81,7 @@ model {
 
         then:
         failure.assertHasDescription("Execution failed for task ':compileMainExecutableMainC'.")
-        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatforms.defaultPlatformName}'"))
+        failure.assertThatCause(Matchers.startsWith("No tool chain is available to build for platform '${NativePlatformsTestFixture.defaultPlatformName}'"))
         failure.assertThatCause(Matchers.containsString("- ${toolChain.instanceDisplayName}: The specified installation directory '${file('does-not-exist')}' does not appear to contain a Windows SDK installation."))
     }
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/DependentSourceSet.java b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/DependentSourceSet.java
index cd9d290..2ae9978 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/DependentSourceSet.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/DependentSourceSet.java
@@ -16,16 +16,16 @@
 package org.gradle.language.nativeplatform;
 
 import org.gradle.api.Incubating;
+import org.gradle.internal.HasInternalProtocol;
 import org.gradle.language.base.LanguageSourceSet;
 
-import java.io.File;
 import java.util.Collection;
-import java.util.Set;
 
 /**
  * A source set that depends on one or more {@link org.gradle.nativeplatform.NativeDependencySet}s to be built.
  */
 @Incubating
+ at HasInternalProtocol
 public interface DependentSourceSet extends LanguageSourceSet {
     /**
      * The libraries that this source set requires.
@@ -38,6 +38,7 @@ public interface DependentSourceSet extends LanguageSourceSet {
      * <ul>
      *     <li>A {@link org.gradle.nativeplatform.NativeLibrarySpec}</li>
      *     <li>A {@link org.gradle.nativeplatform.NativeDependencySet}</li>
+     *     <li>A {@link LanguageSourceSet}</li>
      *     <li>A {@link java.util.Map} containing the library selector.</li>
      * </ul>
      *
@@ -52,20 +53,16 @@ public interface DependentSourceSet extends LanguageSourceSet {
     void lib(Object library);
 
     /**
-     * Adds a pre-compiled header to be used when compiling sources in this source set.
+     * Sets the pre-compiled header to be used when compiling sources in this source set.
      *
      * @param header the header to precompile
      */
-    void preCompiledHeader(String header);
+    void setPreCompiledHeader(String header);
 
     /**
-     * Returns any pre-compiled headers configured for this source set.
+     * Returns the pre-compiled header configured for this source set.
      *
-     * @return the pre-compiled headers
+     * @return the pre-compiled header
      */
-    Set<String> getPreCompiledHeaders();
-
-    File getPrefixHeaderFile();
-
-    void setPrefixHeaderFile(File prefixHeaderFile);
+    String getPreCompiledHeader();
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/DependentSourceSetInternal.java b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/DependentSourceSetInternal.java
new file mode 100644
index 0000000..712ef9c
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/DependentSourceSetInternal.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal;
+
+import org.gradle.language.nativeplatform.DependentSourceSet;
+
+import java.io.File;
+
+public interface DependentSourceSetInternal extends DependentSourceSet {
+    File getPrefixHeaderFile();
+
+    void setPrefixHeaderFile(File prefixHeaderFile);
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/Include.java b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/Include.java
new file mode 100644
index 0000000..64b44ea
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/Include.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal;
+
+public interface Include {
+    String getValue();
+    boolean isImport();
+    IncludeType getType();
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/IncludeType.java b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/IncludeType.java
new file mode 100644
index 0000000..7cc60a2
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/IncludeType.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.nativeplatform.internal;
+
+public enum IncludeType {
+    SYSTEM, QUOTED, MACRO
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/SourceIncludes.java b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/SourceIncludes.java
index 7babeca..7fd879b 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/SourceIncludes.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/language/nativeplatform/internal/SourceIncludes.java
@@ -18,7 +18,9 @@ package org.gradle.language.nativeplatform.internal;
 import java.util.List;
 
 public interface SourceIncludes {
-    List<String> getQuotedIncludes();
-    List<String> getSystemIncludes();
-    List<String> getMacroIncludes();
+    List<Include> getQuotedIncludes();
+    List<Include> getSystemIncludes();
+    List<Include> getMacroIncludes();
+    List<Include> getIncludesAndImports();
+    List<Include> getIncludesOnly();
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeBinarySpec.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeBinarySpec.java
index 6cda4e5..9c4e561 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeBinarySpec.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeBinarySpec.java
@@ -16,6 +16,7 @@
 
 package org.gradle.nativeplatform.internal;
 
+import com.google.common.collect.Maps;
 import org.gradle.api.file.FileCollection;
 import org.gradle.language.nativeplatform.DependentSourceSet;
 import org.gradle.nativeplatform.*;
@@ -27,16 +28,15 @@ import org.gradle.nativeplatform.tasks.ObjectFilesToBinary;
 import org.gradle.nativeplatform.toolchain.NativeToolChain;
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
+import org.gradle.platform.base.ComponentSpec;
 import org.gradle.platform.base.binary.BaseBinarySpec;
 import org.gradle.platform.base.internal.BinaryBuildAbility;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
-import org.gradle.platform.base.internal.ComponentSpecInternal;
 import org.gradle.platform.base.internal.ToolSearchBuildAbility;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.io.File;
+import java.util.*;
 
 public abstract class AbstractNativeBinarySpec extends BaseBinarySpec implements NativeBinarySpecInternal {
     private final Set<? super Object> libs = new LinkedHashSet<Object>();
@@ -50,6 +50,7 @@ public abstract class AbstractNativeBinarySpec extends BaseBinarySpec implements
     private NativePlatform targetPlatform;
     private BuildType buildType;
     private NativeDependencyResolver resolver;
+    private Map<File, PreCompiledHeader> prefixFileToPCH = Maps.newHashMap();
 
     public String getDisplayName() {
         return namingScheme.getDescription();
@@ -59,9 +60,8 @@ public abstract class AbstractNativeBinarySpec extends BaseBinarySpec implements
         return component;
     }
 
-    public void setComponent(NativeComponentSpec component) {
-        this.component = component;
-        setBinarySources(((ComponentSpecInternal) component).getSources().copy(getName()));
+    public void setComponent(ComponentSpec component) {
+        this.component = (NativeComponentSpec) component;
     }
 
     public Flavor getFlavor() {
@@ -128,6 +128,10 @@ public abstract class AbstractNativeBinarySpec extends BaseBinarySpec implements
         return resolve(getSource().withType(DependentSourceSet.class)).getAllLibraryBinaries();
     }
 
+    public Map<File, PreCompiledHeader> getPrefixFileToPCH() {
+        return prefixFileToPCH;
+    }
+
     private NativeBinaryResolveResult resolve(Collection<? extends DependentSourceSet> sourceSets) {
         Set<? super Object> allLibs = new LinkedHashSet<Object>(libs);
         for (DependentSourceSet dependentSourceSet : sourceSets) {
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeComponentSpec.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeComponentSpec.java
index 1718a97..f9ed0e5 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeComponentSpec.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/AbstractNativeComponentSpec.java
@@ -20,12 +20,11 @@ import org.gradle.nativeplatform.NativeComponentSpec;
 import org.gradle.nativeplatform.ObjectFile;
 import org.gradle.platform.base.TransformationFileType;
 import org.gradle.platform.base.component.BaseComponentSpec;
-import org.gradle.platform.base.internal.ComponentSpecInternal;
 import org.gradle.util.GUtil;
 
 import java.util.Set;
 
-public abstract class AbstractNativeComponentSpec extends BaseComponentSpec implements NativeComponentSpec, ComponentSpecInternal {
+public abstract class AbstractNativeComponentSpec extends BaseComponentSpec implements NativeComponentSpec {
     private String baseName;
 
     public String getBaseName() {
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecInternal.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecInternal.java
index 21f559a..da7560d 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecInternal.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecInternal.java
@@ -23,14 +23,16 @@ import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
 import org.gradle.nativeplatform.platform.NativePlatform;
 import org.gradle.nativeplatform.toolchain.NativeToolChain;
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
 import org.gradle.platform.base.internal.BinarySpecInternal;
+import org.gradle.platform.base.internal.ComponentSpecAware;
 
 import java.io.File;
 import java.util.Collection;
+import java.util.Map;
 
-public interface NativeBinarySpecInternal extends NativeBinarySpec, BinarySpecInternal {
-    void setComponent(NativeComponentSpec component);
+public interface NativeBinarySpecInternal extends NativeBinarySpec, BinarySpecInternal, ComponentSpecAware {
 
     void setFlavor(Flavor flavor);
 
@@ -60,4 +62,6 @@ public interface NativeBinarySpecInternal extends NativeBinarySpec, BinarySpecIn
      * Adds some files to include as input to the link/assemble step of this binary.
      */
     void binaryInputs(FileCollection files);
+
+    Map<File, PreCompiledHeader> getPrefixFileToPCH();
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativePlatformResolver.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativePlatformResolver.java
index bf71b87..0a68596 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativePlatformResolver.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/NativePlatformResolver.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.nativeplatform.internal;
 
-import com.google.common.collect.Sets;
 import org.gradle.api.specs.Spec;
 import org.gradle.nativeplatform.platform.NativePlatform;
 import org.gradle.nativeplatform.platform.internal.NativePlatforms;
@@ -23,13 +22,11 @@ import org.gradle.platform.base.internal.PlatformRequirement;
 import org.gradle.platform.base.internal.PlatformResolver;
 import org.gradle.util.CollectionUtils;
 
-import java.util.Set;
-
 public class NativePlatformResolver implements PlatformResolver<NativePlatform> {
-    private final Set<NativePlatform> nativePlatforms = Sets.newHashSet();
+    private final NativePlatforms nativePlatforms;
 
-    public NativePlatformResolver() {
-        nativePlatforms.addAll(NativePlatforms.defaultPlatformDefinitions());
+    public NativePlatformResolver(NativePlatforms nativePlatforms) {
+        this.nativePlatforms = nativePlatforms;
     }
 
     public Class<NativePlatform> getType() {
@@ -38,8 +35,7 @@ public class NativePlatformResolver implements PlatformResolver<NativePlatform>
 
     @Override
     public NativePlatform resolve(final PlatformRequirement platformRequirement) {
-        NativePlatforms.defaultPlatformDefinitions();
-        return CollectionUtils.findFirst(nativePlatforms, new Spec<NativePlatform>() {
+        return CollectionUtils.findFirst(nativePlatforms.defaultPlatformDefinitions(), new Spec<NativePlatform>() {
             @Override
             public boolean isSatisfiedBy(NativePlatform element) {
                 return element.getName().equals(platformRequirement.getPlatformName());
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactory.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactory.java
deleted file mode 100644
index d41a147..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.nativeplatform.internal.configure;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.nativeplatform.*;
-import org.gradle.nativeplatform.internal.*;
-import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
-import org.gradle.nativeplatform.platform.NativePlatform;
-import org.gradle.nativeplatform.toolchain.NativeToolChain;
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.binary.BaseBinarySpec;
-import org.gradle.platform.base.internal.BinaryNamingScheme;
-import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder;
-
-public class DefaultNativeBinariesFactory implements NativeBinariesFactory {
-    private final Instantiator instantiator;
-    private final Action<NativeBinarySpec> configureAction;
-    private final NativeDependencyResolver resolver;
-    private final ITaskFactory taskFactory;
-
-    public DefaultNativeBinariesFactory(Instantiator instantiator, Action<NativeBinarySpec> configureAction, NativeDependencyResolver resolver, ITaskFactory taskFactory) {
-        this.configureAction = configureAction;
-        this.instantiator = instantiator;
-        this.resolver = resolver;
-        this.taskFactory = taskFactory;
-    }
-
-    public void createNativeBinaries(NativeComponentSpec component, BinaryNamingSchemeBuilder namingScheme, NativeToolChain toolChain, PlatformToolProvider toolProvider, NativePlatform platform, BuildType buildType, Flavor flavor) {
-        if (component instanceof NativeLibrarySpec) {
-            createNativeBinary(DefaultSharedLibraryBinarySpec.class, component, namingScheme.withTypeString("SharedLibrary").build(), toolChain, toolProvider, platform, buildType, flavor);
-            createNativeBinary(DefaultStaticLibraryBinarySpec.class, component, namingScheme.withTypeString("StaticLibrary").build(), toolChain, toolProvider, platform, buildType, flavor);
-        } else {
-            createNativeBinary(DefaultNativeExecutableBinarySpec.class, component, namingScheme.withTypeString("Executable").build(), toolChain, toolProvider, platform, buildType, flavor);
-        }
-    }
-    private <T extends AbstractNativeBinarySpec> void createNativeBinary(Class<T> type, NativeComponentSpec component, BinaryNamingScheme namingScheme,
-                                                                         NativeToolChain toolChain, PlatformToolProvider toolProvider, NativePlatform platform, BuildType buildType, Flavor flavor) {
-        T nativeBinary = create(type, instantiator, component, namingScheme, resolver, toolChain, toolProvider, platform, buildType, flavor, taskFactory);
-        setupDefaults(nativeBinary);
-        component.getBinaries().add(nativeBinary);
-    }
-
-    public static <T extends AbstractNativeBinarySpec> T create(Class<T> type, Instantiator instantiator,
-                                                                NativeComponentSpec component, BinaryNamingScheme namingScheme, NativeDependencyResolver resolver,
-                                                                NativeToolChain toolChain, PlatformToolProvider toolProvider, NativePlatform platform, BuildType buildType, Flavor flavor, ITaskFactory taskFactory) {
-        T nativeBinary = BaseBinarySpec.create(type, namingScheme.getLifecycleTaskName(), instantiator, taskFactory);
-        nativeBinary.setNamingScheme(namingScheme);
-        nativeBinary.setComponent(component);
-        nativeBinary.setTargetPlatform(platform);
-        nativeBinary.setToolChain(toolChain);
-        nativeBinary.setPlatformToolProvider(toolProvider);
-        nativeBinary.setBuildType(buildType);
-        nativeBinary.setFlavor(flavor);
-        nativeBinary.setResolver(resolver);
-        return nativeBinary;
-    }
-
-    private void setupDefaults(NativeBinarySpec nativeBinary) {
-        configureAction.execute(nativeBinary);
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaries.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaries.java
new file mode 100644
index 0000000..54aee6c
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaries.java
@@ -0,0 +1,93 @@
+/*
+ * 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.nativeplatform.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.model.ModelMap;
+import org.gradle.nativeplatform.*;
+import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
+import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.platform.base.internal.BinaryNamingScheme;
+import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder;
+
+public class NativeBinaries {
+
+    public static void createNativeBinaries(
+        NativeComponentSpec component,
+        ModelMap<NativeBinarySpec> binaries,
+        NativeDependencyResolver resolver,
+        BinaryNamingSchemeBuilder namingScheme,
+        NativePlatform platform,
+        BuildType buildType,
+        Flavor flavor
+    ) {
+        if (component instanceof NativeLibrarySpec) {
+            createNativeBinary(SharedLibraryBinarySpec.class, binaries, resolver, namingScheme.withTypeString("SharedLibrary").build(), platform, buildType, flavor);
+            createNativeBinary(StaticLibraryBinarySpec.class, binaries, resolver, namingScheme.withTypeString("StaticLibrary").build(), platform, buildType, flavor);
+        } else {
+            createNativeBinary(NativeExecutableBinarySpec.class, binaries, resolver, namingScheme.withTypeString("Executable").build(), platform, buildType, flavor);
+        }
+    }
+
+    private static <T extends NativeBinarySpec> void createNativeBinary(
+        Class<T> type,
+        ModelMap<NativeBinarySpec> binaries,
+        final NativeDependencyResolver resolver,
+        final BinaryNamingScheme namingScheme,
+        final NativePlatform platform,
+        final BuildType buildType,
+        final Flavor flavor
+    ) {
+        final String name = namingScheme.getLifecycleTaskName();
+        binaries.create(name, type);
+
+        // This is horrendously bad.
+        // We need to set the platform, _before_ the @Defaults rules of NativeBinaryRules assign the toolchain.
+        // We can't just assign the toolchain here because the initializer would be closing over the toolchain which is not reusable, and this breaks model reuse.
+        // So here we are just closing over the safely reusable things and then using proper dependencies for the tool chain registry.
+        // Unfortunately, we can't do it in the create action because that would fire _after_ @Defaults rules.
+        // We have to use a @Defaults rule to assign the tool chain because it needs to be there in user @Mutate rules
+        // Or at least, the file locations do so that they can be tweaked.
+        // LD - 5/6/14
+        binaries.beforeEach(NativeBinarySpec.class, new Action<NativeBinarySpec>() {
+            @Override
+            public void execute(NativeBinarySpec nativeBinarySpec) {
+                if (nativeBinarySpec.getName().equals(name)) {
+                    initialize(nativeBinarySpec, namingScheme, resolver, platform, buildType, flavor);
+                }
+            }
+        });
+        binaries.named(name, NativeBinaryRules.class);
+    }
+
+    public static void initialize(
+        NativeBinarySpec nativeBinarySpec,
+        BinaryNamingScheme namingScheme,
+        NativeDependencyResolver resolver,
+        NativePlatform platform,
+        BuildType buildType,
+        Flavor flavor
+    ) {
+        NativeBinarySpecInternal nativeBinary = (NativeBinarySpecInternal) nativeBinarySpec;
+        nativeBinary.setNamingScheme(namingScheme);
+        nativeBinary.setTargetPlatform(platform);
+        nativeBinary.setBuildType(buildType);
+        nativeBinary.setFlavor(flavor);
+        nativeBinary.setResolver(resolver);
+    }
+
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinariesFactory.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinariesFactory.java
deleted file mode 100644
index f9fca11..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinariesFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.nativeplatform.internal.configure;
-
-import org.gradle.nativeplatform.NativeComponentSpec;
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder;
-import org.gradle.nativeplatform.BuildType;
-import org.gradle.nativeplatform.Flavor;
-import org.gradle.nativeplatform.platform.NativePlatform;
-import org.gradle.nativeplatform.toolchain.NativeToolChain;
-
-public interface NativeBinariesFactory {
-    void createNativeBinaries(NativeComponentSpec component, BinaryNamingSchemeBuilder namingScheme, NativeToolChain toolChain, PlatformToolProvider toolProvider, NativePlatform platform, BuildType buildType, Flavor flavor);
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRules.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRules.java
new file mode 100644
index 0000000..2e23023
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRules.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.internal.configure;
+
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.language.base.internal.registry.LanguageTransform;
+import org.gradle.language.base.internal.registry.LanguageTransformContainer;
+import org.gradle.model.Defaults;
+import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.NativeBinarySpec;
+import org.gradle.nativeplatform.NativeExecutableBinarySpec;
+import org.gradle.nativeplatform.SharedLibraryBinarySpec;
+import org.gradle.nativeplatform.StaticLibraryBinarySpec;
+import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainRegistryInternal;
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
+import org.gradle.platform.base.internal.BinaryNamingScheme;
+
+import java.io.File;
+import java.util.Map;
+
+public class NativeBinaryRules extends RuleSource {
+    @Defaults
+    void addToolExtensions(NativeBinarySpec nativeBinarySpec, LanguageTransformContainer languageTransforms) {
+        for (LanguageTransform<?, ?> language : languageTransforms) {
+            Map<String, Class<?>> binaryTools = language.getBinaryTools();
+            for (String toolName : binaryTools.keySet()) {
+                ((ExtensionAware) nativeBinarySpec).getExtensions().create(toolName, binaryTools.get(toolName));
+            }
+        }
+    }
+
+    @Defaults
+    public static void assignTools(NativeBinarySpec nativeBinarySpec, NativeToolChainRegistryInternal toolChains, /* @Path("buildDir") */ File buildDir) {
+        NativeBinarySpecInternal binarySpecInternal = (NativeBinarySpecInternal) nativeBinarySpec;
+
+        NativeToolChainInternal toolChain = (NativeToolChainInternal) toolChains.getForPlatform(nativeBinarySpec.getTargetPlatform());
+        binarySpecInternal.setToolChain(toolChain);
+        PlatformToolProvider toolProvider = toolChain.select((NativePlatformInternal) nativeBinarySpec.getTargetPlatform());
+        binarySpecInternal.setPlatformToolProvider(toolProvider);
+
+        File binariesOutputDir = new File(buildDir, "binaries");
+        BinaryNamingScheme namingScheme = binarySpecInternal.getNamingScheme();
+        File binaryOutputDir = new File(binariesOutputDir, namingScheme.getOutputDirectoryBase());
+        String baseName = binarySpecInternal.getComponent().getBaseName();
+
+        if (binarySpecInternal instanceof NativeExecutableBinarySpec) {
+            ((NativeExecutableBinarySpec) binarySpecInternal).setExecutableFile(new File(binaryOutputDir, toolProvider.getExecutableName(baseName)));
+        } else if (binarySpecInternal instanceof SharedLibraryBinarySpec) {
+            ((SharedLibraryBinarySpec) binarySpecInternal).setSharedLibraryFile(new File(binaryOutputDir, toolProvider.getSharedLibraryName(baseName)));
+            ((SharedLibraryBinarySpec) binarySpecInternal).setSharedLibraryLinkFile(new File(binaryOutputDir, toolProvider.getSharedLibraryLinkFileName(baseName)));
+        } else if (binarySpecInternal instanceof StaticLibraryBinarySpec) {
+            ((StaticLibraryBinarySpec) binarySpecInternal).setStaticLibraryFile(new File(binaryOutputDir, toolProvider.getStaticLibraryName(baseName)));
+        }
+    }
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializer.java
deleted file mode 100644
index 7a50824..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.nativeplatform.internal.configure;
-
-import org.gradle.api.Action;
-import org.gradle.nativeplatform.NativeBinarySpec;
-import org.gradle.nativeplatform.NativeExecutableBinarySpec;
-import org.gradle.nativeplatform.SharedLibraryBinarySpec;
-import org.gradle.nativeplatform.StaticLibraryBinarySpec;
-import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.internal.BinaryNamingScheme;
-
-import java.io.File;
-
-public class NativeBinarySpecInitializer implements Action<NativeBinarySpec> {
-    private final File binariesOutputDir;
-
-    public NativeBinarySpecInitializer(File buildDir) {
-        binariesOutputDir = new File(buildDir, "binaries");
-    }
-
-    public void execute(NativeBinarySpec nativeBinary) {
-        BinaryNamingScheme namingScheme = ((NativeBinarySpecInternal) nativeBinary).getNamingScheme();
-        PlatformToolProvider toolProvider = ((NativeBinarySpecInternal) nativeBinary).getPlatformToolProvider();
-        File binaryOutputDir = new File(binariesOutputDir, namingScheme.getOutputDirectoryBase());
-        String baseName = nativeBinary.getComponent().getBaseName();
-
-        if (nativeBinary instanceof NativeExecutableBinarySpec) {
-            ((NativeExecutableBinarySpec) nativeBinary).setExecutableFile(new File(binaryOutputDir, toolProvider.getExecutableName(baseName)));
-        } else if (nativeBinary instanceof SharedLibraryBinarySpec) {
-            ((SharedLibraryBinarySpec) nativeBinary).setSharedLibraryFile(new File(binaryOutputDir, toolProvider.getSharedLibraryName(baseName)));
-            ((SharedLibraryBinarySpec) nativeBinary).setSharedLibraryLinkFile(new File(binaryOutputDir, toolProvider.getSharedLibraryLinkFileName(baseName)));
-        } else if (nativeBinary instanceof StaticLibraryBinarySpec) {
-            ((StaticLibraryBinarySpec) nativeBinary).setStaticLibraryFile(new File(binaryOutputDir, toolProvider.getStaticLibraryName(baseName)));
-        }
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRules.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRules.java
new file mode 100644
index 0000000..eb4a7a7
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRules.java
@@ -0,0 +1,169 @@
+/*
+ * 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.nativeplatform.internal.configure;
+
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
+import org.gradle.model.Defaults;
+import org.gradle.model.Finalize;
+import org.gradle.model.RuleSource;
+import org.gradle.nativeplatform.*;
+import org.gradle.nativeplatform.internal.TargetedNativeComponentInternal;
+import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
+import org.gradle.nativeplatform.platform.internal.NativePlatforms;
+import org.gradle.platform.base.internal.*;
+import org.gradle.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Cross cutting rules for all instances of {@link org.gradle.nativeplatform.NativeComponentSpec}
+ */
+ at SuppressWarnings("UnusedDeclaration")
+public class NativeComponentRules extends RuleSource {
+    @Defaults
+    public void applyHeaderSourceSetConventions(final NativeComponentSpec component) {
+        component.getSource().withType(HeaderExportingSourceSet.class).afterEach(new Action<HeaderExportingSourceSet>() {
+            @Override
+            public void execute(HeaderExportingSourceSet headerSourceSet) {
+                // Only apply default locations when none explicitly configured
+                if (headerSourceSet.getExportedHeaders().getSrcDirs().isEmpty()) {
+                    headerSourceSet.getExportedHeaders().srcDir(String.format("src/%s/headers", component.getName()));
+                }
+
+                headerSourceSet.getImplicitHeaders().setSrcDirs(headerSourceSet.getSource().getSrcDirs());
+                headerSourceSet.getImplicitHeaders().include("**/*.h");
+            }
+        });
+    }
+
+    @Finalize
+    public static void createBinaries(
+        NativeComponentSpec nativeComponent,
+        PlatformResolvers platforms,
+        BuildTypeContainer buildTypes,
+        FlavorContainer flavors,
+        ServiceRegistry serviceRegistry
+    ) {
+        final NativePlatforms nativePlatforms = serviceRegistry.get(NativePlatforms.class);
+        final NativeDependencyResolver nativeDependencyResolver = serviceRegistry.get(NativeDependencyResolver.class);
+
+        createBinariesImpl(nativeComponent, platforms, buildTypes, flavors, nativePlatforms, nativeDependencyResolver, new DefaultBinaryNamingSchemeBuilder());
+    }
+
+    static void createBinariesImpl(
+        NativeComponentSpec nativeComponent,
+        PlatformResolvers platforms,
+        Set<? extends BuildType> buildTypes,
+        Set<? extends Flavor> flavors,
+        NativePlatforms nativePlatforms,
+        NativeDependencyResolver nativeDependencyResolver,
+        BinaryNamingSchemeBuilder namingSchemeBuilder
+    ) {
+        if (!(nativeComponent instanceof TargetedNativeComponentInternal)) {
+            return;
+        }
+        TargetedNativeComponentInternal targetedComponent = (TargetedNativeComponentInternal) nativeComponent;
+        List<NativePlatform> resolvedPlatforms = resolvePlatforms(targetedComponent, nativePlatforms, platforms);
+
+        for (NativePlatform platform : resolvedPlatforms) {
+            BinaryNamingSchemeBuilder builder = namingSchemeBuilder.withComponentName(nativeComponent.getName());
+            builder = maybeAddDimension(builder, resolvedPlatforms, platform.getName());
+            executeForEachBuildType(
+                nativeComponent,
+                (NativePlatformInternal) platform,
+                builder,
+                buildTypes,
+                flavors,
+                nativeDependencyResolver
+            );
+        }
+    }
+
+    private static List<NativePlatform> resolvePlatforms(TargetedNativeComponentInternal targetedComponent, NativePlatforms nativePlatforms, final PlatformResolvers platforms) {
+        List<PlatformRequirement> targetPlatforms = targetedComponent.getTargetPlatforms();
+        if (targetPlatforms.isEmpty()) {
+            PlatformRequirement requirement = DefaultPlatformRequirement.create(nativePlatforms.getDefaultPlatformName());
+            targetPlatforms = Collections.singletonList(requirement);
+        }
+        return CollectionUtils.collect(targetPlatforms, new Transformer<NativePlatform, PlatformRequirement>() {
+            @Override
+            public NativePlatform transform(PlatformRequirement platformRequirement) {
+                return platforms.resolve(NativePlatform.class, platformRequirement);
+            }
+        });
+    }
+
+    private static BinaryNamingSchemeBuilder maybeAddDimension(BinaryNamingSchemeBuilder builder, Collection<?> variations, String name) {
+        if (variations.size() > 1) {
+            builder = builder.withVariantDimension(name);
+        }
+        return builder;
+    }
+
+    private static void executeForEachBuildType(
+        NativeComponentSpec projectNativeComponent,
+        NativePlatformInternal platform,
+        BinaryNamingSchemeBuilder builder,
+        Set<? extends BuildType> allBuildTypes,
+        Set<? extends Flavor> allFlavors,
+        NativeDependencyResolver nativeDependencyResolver
+    ) {
+        Set<BuildType> targetBuildTypes = ((TargetedNativeComponentInternal) projectNativeComponent).chooseBuildTypes(allBuildTypes);
+        for (BuildType buildType : targetBuildTypes) {
+            BinaryNamingSchemeBuilder nameBuilder = maybeAddDimension(builder, targetBuildTypes, buildType.getName());
+            executeForEachFlavor(
+                projectNativeComponent,
+                platform,
+                buildType,
+                nameBuilder,
+                allFlavors,
+                nativeDependencyResolver
+            );
+        }
+    }
+
+    private static void executeForEachFlavor(
+        NativeComponentSpec projectNativeComponent,
+        NativePlatform platform,
+        BuildType buildType,
+        BinaryNamingSchemeBuilder buildTypedNameBuilder,
+        Set<? extends Flavor> allFlavors,
+        NativeDependencyResolver nativeDependencyResolver
+    ) {
+        Set<Flavor> targetFlavors = ((TargetedNativeComponentInternal) projectNativeComponent).chooseFlavors(allFlavors);
+        for (Flavor flavor : targetFlavors) {
+            BinaryNamingSchemeBuilder flavoredNameBuilder = maybeAddDimension(buildTypedNameBuilder, targetFlavors, flavor.getName());
+            NativeBinaries.createNativeBinaries(
+                projectNativeComponent,
+                projectNativeComponent.getBinaries().withType(NativeBinarySpec.class),
+                nativeDependencyResolver,
+                flavoredNameBuilder,
+                platform,
+                buildType,
+                flavor
+            );
+        }
+    }
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializer.java
deleted file mode 100644
index e20fa0e..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializer.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.nativeplatform.internal.configure;
-
-import org.gradle.api.Action;
-import org.gradle.api.Named;
-import org.gradle.api.Transformer;
-import org.gradle.nativeplatform.BuildType;
-import org.gradle.nativeplatform.Flavor;
-import org.gradle.nativeplatform.NativeComponentSpec;
-import org.gradle.nativeplatform.internal.TargetedNativeComponentInternal;
-import org.gradle.nativeplatform.platform.NativePlatform;
-import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
-import org.gradle.nativeplatform.platform.internal.NativePlatforms;
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainRegistryInternal;
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.internal.PlatformResolvers;
-import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder;
-import org.gradle.platform.base.internal.DefaultPlatformRequirement;
-import org.gradle.platform.base.internal.PlatformRequirement;
-import org.gradle.util.CollectionUtils;
-
-import java.util.*;
-
-public class NativeComponentSpecInitializer implements Action<NativeComponentSpec> {
-    private final NativeBinariesFactory factory;
-    private final NativeToolChainRegistryInternal toolChainRegistry;
-    private final PlatformResolvers platforms;
-    private final Set<BuildType> allBuildTypes = new LinkedHashSet<BuildType>();
-    private final Set<Flavor> allFlavors = new LinkedHashSet<Flavor>();
-
-    private final BinaryNamingSchemeBuilder namingSchemeBuilder;
-
-    public NativeComponentSpecInitializer(NativeBinariesFactory factory, BinaryNamingSchemeBuilder namingSchemeBuilder, NativeToolChainRegistryInternal toolChainRegistry,
-                                          PlatformResolvers platforms, Collection<? extends BuildType> allBuildTypes, Collection<? extends Flavor> allFlavors) {
-        this.factory = factory;
-        this.namingSchemeBuilder = namingSchemeBuilder;
-        this.toolChainRegistry = toolChainRegistry;
-        this.allBuildTypes.addAll(allBuildTypes);
-        this.allFlavors.addAll(allFlavors);
-        this.platforms = platforms;
-    }
-
-    public void execute(NativeComponentSpec projectNativeComponent) {
-        TargetedNativeComponentInternal targetedComponent = (TargetedNativeComponentInternal) projectNativeComponent;
-        List<NativePlatform> resolvedPlatforms = resolvePlatforms(targetedComponent);
-
-        for (NativePlatform platform: resolvedPlatforms) {
-            NativeToolChainInternal toolChain = (NativeToolChainInternal) toolChainRegistry.getForPlatform(platform);
-            PlatformToolProvider toolProvider = toolChain.select((NativePlatformInternal) platform);
-
-            BinaryNamingSchemeBuilder builder = namingSchemeBuilder.withComponentName(projectNativeComponent.getName());
-            builder = maybeAddDimension(builder, platform, resolvedPlatforms);
-            executeForEachBuildType(projectNativeComponent, (NativePlatformInternal) platform, builder, toolChain, toolProvider);
-        }
-    }
-
-    private List<NativePlatform> resolvePlatforms(TargetedNativeComponentInternal targetedComponent) {
-        List<PlatformRequirement> targetPlatforms = targetedComponent.getTargetPlatforms();
-        if (targetPlatforms.isEmpty()) {
-            PlatformRequirement requirement = DefaultPlatformRequirement.create(NativePlatforms.getDefaultPlatformName());
-            targetPlatforms = Collections.singletonList(requirement);
-        }
-        return CollectionUtils.collect(targetPlatforms, new Transformer<NativePlatform, PlatformRequirement>() {
-            @Override
-            public NativePlatform transform(PlatformRequirement platformRequirement) {
-                return platforms.resolve(NativePlatform.class, platformRequirement);
-            }
-        });
-    }
-
-    private void executeForEachBuildType(NativeComponentSpec projectNativeComponent, NativePlatformInternal platform, BinaryNamingSchemeBuilder builder, NativeToolChainInternal toolChain, PlatformToolProvider toolProvider) {
-        Set<BuildType> targetBuildTypes = ((TargetedNativeComponentInternal) projectNativeComponent).chooseBuildTypes(allBuildTypes);
-        for (BuildType buildType : targetBuildTypes) {
-            BinaryNamingSchemeBuilder nameBuilder = maybeAddDimension(builder, buildType, targetBuildTypes);
-            executeForEachFlavor(projectNativeComponent, platform, buildType, nameBuilder, toolChain, toolProvider);
-        }
-    }
-
-    private void executeForEachFlavor(NativeComponentSpec projectNativeComponent, NativePlatform platform, BuildType buildType, BinaryNamingSchemeBuilder buildTypedNameBuilder, NativeToolChainInternal toolChain, PlatformToolProvider toolProvider) {
-        Set<Flavor> targetFlavors = ((TargetedNativeComponentInternal) projectNativeComponent).chooseFlavors(allFlavors);
-        for (Flavor flavor : targetFlavors) {
-            BinaryNamingSchemeBuilder flavoredNameBuilder = maybeAddDimension(buildTypedNameBuilder, flavor, targetFlavors);
-            factory.createNativeBinaries(projectNativeComponent, flavoredNameBuilder, toolChain, toolProvider, platform, buildType, flavor);
-        }
-    }
-
-    private <T extends Named> BinaryNamingSchemeBuilder maybeAddDimension(BinaryNamingSchemeBuilder builder, T variation, Collection<T> variations) {
-        if (variations.size() > 1) {
-            builder = builder.withVariantDimension(variation.getName());
-        }
-        return builder;
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/DefaultPreCompiledHeaderTransformContainer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/DefaultPreCompiledHeaderTransformContainer.java
deleted file mode 100644
index e4170f3..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/DefaultPreCompiledHeaderTransformContainer.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.internal.pch;
-
-import com.google.common.reflect.TypeToken;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.language.base.internal.registry.LanguageTransform;
-
-public class DefaultPreCompiledHeaderTransformContainer extends DefaultDomainObjectSet<LanguageTransform<?, ?>> implements PreCompiledHeaderTransformContainer {
-    public DefaultPreCompiledHeaderTransformContainer() {
-        super(getLanguageTransformType());
-    }
-
-    private static Class<LanguageTransform<?, ?>> getLanguageTransformType() {
-        @SuppressWarnings("unchecked")
-        Class<LanguageTransform<?, ?>> rawType = (Class<LanguageTransform<?, ?>>) new TypeToken<LanguageTransform<?, ?>>() {}.getRawType();
-        return rawType;
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PchEnabledLanguageTransform.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PchEnabledLanguageTransform.java
new file mode 100644
index 0000000..90be809
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PchEnabledLanguageTransform.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.internal.pch;
+
+import org.gradle.language.base.internal.SourceTransformTaskConfig;
+import org.gradle.language.base.internal.registry.LanguageTransform;
+import org.gradle.language.nativeplatform.DependentSourceSet;
+import org.gradle.nativeplatform.ObjectFile;
+
+public interface PchEnabledLanguageTransform<U extends DependentSourceSet> extends LanguageTransform<U, ObjectFile> {
+    SourceTransformTaskConfig getPchTransformTask();
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PreCompiledHeaderTransformContainer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PreCompiledHeaderTransformContainer.java
deleted file mode 100644
index a2bb5e0..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/pch/PreCompiledHeaderTransformContainer.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.internal.pch;
-
-import org.gradle.api.DomainObjectSet;
-import org.gradle.language.base.internal.registry.LanguageTransform;
-
-public interface PreCompiledHeaderTransformContainer extends DomainObjectSet<LanguageTransform<?, ?>> {
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/prebuilt/PrebuiltLibraryInitializer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/prebuilt/PrebuiltLibraryInitializer.java
index 301796f..a468b12 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/prebuilt/PrebuiltLibraryInitializer.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/prebuilt/PrebuiltLibraryInitializer.java
@@ -39,10 +39,11 @@ public class PrebuiltLibraryInitializer implements Action<PrebuiltLibrary> {
     private final Set<Flavor> allFlavors = new LinkedHashSet<Flavor>();
 
     public PrebuiltLibraryInitializer(Instantiator instantiator,
+                                      NativePlatforms nativePlatforms,
                                       Collection<? extends NativePlatform> allPlatforms, Collection<? extends BuildType> allBuildTypes, Collection<? extends Flavor> allFlavors) {
         this.instantiator = instantiator;
         this.allPlatforms.addAll(allPlatforms);
-        this.allPlatforms.addAll(NativePlatforms.defaultPlatformDefinitions());
+        this.allPlatforms.addAll(nativePlatforms.defaultPlatformDefinitions());
         this.allBuildTypes.addAll(allBuildTypes);
         this.allFlavors.addAll(allFlavors);
     }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/DefaultLibraryResolver.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/DefaultLibraryResolver.java
index 24506f4..3a6e629 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/DefaultLibraryResolver.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/DefaultLibraryResolver.java
@@ -17,7 +17,6 @@ package org.gradle.nativeplatform.internal.resolve;
 
 import org.gradle.api.DomainObjectSet;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.nativeplatform.*;
 import org.gradle.nativeplatform.platform.NativePlatform;
 import org.gradle.util.GUtil;
@@ -25,13 +24,11 @@ import org.gradle.util.GUtil;
 import java.util.Set;
 
 class DefaultLibraryResolver {
-    private final Instantiator instantiator;
     private final NativeLibraryRequirement requirement;
     private final NativeBinarySpec context;
     private final LibraryBinaryLocator libraryBinaryLocator;
 
-    public DefaultLibraryResolver(LibraryBinaryLocator libraryBinaryLocator, Instantiator instantiator, NativeLibraryRequirement requirement, NativeBinarySpec context) {
-        this.instantiator = instantiator;
+    public DefaultLibraryResolver(LibraryBinaryLocator libraryBinaryLocator, NativeLibraryRequirement requirement, NativeBinarySpec context) {
         this.requirement = requirement;
         this.context = context;
         this.libraryBinaryLocator = libraryBinaryLocator;
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/LibraryNativeDependencyResolver.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/LibraryNativeDependencyResolver.java
index a7cfaa7..0812995 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/LibraryNativeDependencyResolver.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/LibraryNativeDependencyResolver.java
@@ -16,21 +16,18 @@
 
 package org.gradle.nativeplatform.internal.resolve;
 
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.nativeplatform.NativeLibraryBinary;
 
 public class LibraryNativeDependencyResolver implements NativeDependencyResolver {
     private final LibraryBinaryLocator libraryBinaryLocator;
-    private final Instantiator instantiator;
 
-    public LibraryNativeDependencyResolver(final LibraryBinaryLocator locator, Instantiator instantiator) {
+    public LibraryNativeDependencyResolver(final LibraryBinaryLocator locator) {
         libraryBinaryLocator = locator;
-        this.instantiator = instantiator;
     }
 
     public void resolve(NativeBinaryResolveResult resolution) {
         for (NativeBinaryRequirementResolveResult requirementResolution : resolution.getPendingResolutions()) {
-            DefaultLibraryResolver libraryResolver = new DefaultLibraryResolver(libraryBinaryLocator, instantiator, requirementResolution.getRequirement(), resolution.getTarget());
+            DefaultLibraryResolver libraryResolver = new DefaultLibraryResolver(libraryBinaryLocator, requirementResolution.getRequirement(), resolution.getTarget());
             NativeLibraryBinary libraryBinary = libraryResolver.resolveLibraryBinary();
             requirementResolution.setLibraryBinary(libraryBinary);
             requirementResolution.setNativeDependencySet(new DefaultNativeDependencySet(libraryBinary));
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/NativeDependencyResolverServices.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/NativeDependencyResolverServices.java
index c02714c..1e7f5b1 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/NativeDependencyResolverServices.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/NativeDependencyResolverServices.java
@@ -17,7 +17,6 @@ package org.gradle.nativeplatform.internal.resolve;
 
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.nativeplatform.internal.prebuilt.PrebuiltLibraryBinaryLocator;
 
 import java.util.ArrayList;
@@ -37,8 +36,8 @@ public class NativeDependencyResolverServices {
         return new ChainedLibraryBinaryLocator(locators);
     }
 
-    public NativeDependencyResolver createResolver(LibraryBinaryLocator locator, Instantiator instantiator) {
-        NativeDependencyResolver resolver = new LibraryNativeDependencyResolver(locator, instantiator);
+    public NativeDependencyResolver createResolver(LibraryBinaryLocator locator) {
+        NativeDependencyResolver resolver = new LibraryNativeDependencyResolver(locator);
         resolver = new ApiRequirementNativeDependencyResolver(resolver);
         resolver = new RequirementParsingNativeDependencyResolver(resolver);
         resolver = new SourceSetNativeDependencyResolver(resolver);
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocator.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocator.java
index 5910bab..ded54d3 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocator.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocator.java
@@ -16,12 +16,17 @@
 package org.gradle.nativeplatform.internal.resolve;
 
 import org.gradle.api.DomainObjectSet;
-import org.gradle.api.Project;
+import org.gradle.api.UnknownDomainObjectException;
 import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.nativeplatform.NativeLibrarySpec;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.model.ModelMap;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
+import org.gradle.nativeplatform.NativeBinarySpec;
 import org.gradle.nativeplatform.NativeLibraryBinary;
 import org.gradle.nativeplatform.NativeLibraryRequirement;
-import org.gradle.nativeplatform.NativeBinarySpec;
+import org.gradle.nativeplatform.NativeLibrarySpec;
 import org.gradle.platform.base.ComponentSpecContainer;
 
 public class ProjectLibraryBinaryLocator implements LibraryBinaryLocator {
@@ -33,21 +38,27 @@ public class ProjectLibraryBinaryLocator implements LibraryBinaryLocator {
 
     // Converts the binaries of a project library into regular binary instances
     public DomainObjectSet<NativeLibraryBinary> getBinaries(NativeLibraryRequirement requirement) {
-        Project project = findProject(requirement);
-        ComponentSpecContainer componentSpecContainer = project.getExtensions().findByType(ComponentSpecContainer.class);
-        if (componentSpecContainer == null) {
+        ProjectInternal project = findProject(requirement);
+        ModelRegistry modelRegistry = project.getModelRegistry();
+        ComponentSpecContainer components = modelRegistry.find(ModelPath.path("components"), ModelType.of(ComponentSpecContainer.class));
+        if (components == null) {
             throw new LibraryResolveException(String.format("Project does not have a libraries container: '%s'", project.getPath()));
         }
-        DomainObjectSet<NativeBinarySpec> projectBinaries = componentSpecContainer.withType(NativeLibrarySpec.class).getByName(requirement.getLibraryName()).getBinaries().withType(NativeBinarySpec.class);
+        String libraryName = requirement.getLibraryName();
+        NativeLibrarySpec library = components.withType(NativeLibrarySpec.class).get(libraryName);
+        if (library == null) {
+            throw new UnknownDomainObjectException(String.format("%s with name '%s' not found.", NativeLibrarySpec.class.getSimpleName(), libraryName));
+        }
+        ModelMap<NativeBinarySpec> projectBinaries = library.getBinaries().withType(NativeBinarySpec.class);
         DomainObjectSet<NativeLibraryBinary> binaries = new DefaultDomainObjectSet<NativeLibraryBinary>(NativeLibraryBinary.class);
         // TODO:DAZ Convert, don't cast
-        for (NativeBinarySpec nativeBinarySpec : projectBinaries) {
+        for (NativeBinarySpec nativeBinarySpec : projectBinaries.values()) {
             binaries.add((NativeLibraryBinary) nativeBinarySpec);
         }
         return binaries;
     }
 
-    private Project findProject(NativeLibraryRequirement requirement) {
+    private ProjectInternal findProject(NativeLibraryRequirement requirement) {
         return projectLocator.locateProject(requirement.getProjectPath());
     }
 
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/services/NativeBinaryServices.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/services/NativeBinaryServices.java
index 27bcc00..f8908b9 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/services/NativeBinaryServices.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/internal/services/NativeBinaryServices.java
@@ -19,9 +19,11 @@ package org.gradle.nativeplatform.internal.services;
 import org.gradle.internal.service.ServiceRegistration;
 import org.gradle.internal.service.scopes.PluginServiceRegistry;
 import org.gradle.nativeplatform.internal.NativeExecutableBinaryRenderer;
+import org.gradle.nativeplatform.internal.NativePlatformResolver;
 import org.gradle.nativeplatform.internal.SharedLibraryBinaryRenderer;
 import org.gradle.nativeplatform.internal.StaticLibraryBinaryRenderer;
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolverServices;
+import org.gradle.nativeplatform.platform.internal.NativePlatforms;
 import org.gradle.nativeplatform.test.internal.NativeTestSuiteBinaryRenderer;
 import org.gradle.nativeplatform.toolchain.internal.gcc.version.CompilerMetaDataProviderFactory;
 import org.gradle.nativeplatform.toolchain.internal.msvcpp.DefaultVisualStudioLocator;
@@ -33,6 +35,8 @@ public class NativeBinaryServices implements PluginServiceRegistry {
         registration.add(StaticLibraryBinaryRenderer.class);
         registration.add(NativeExecutableBinaryRenderer.class);
         registration.add(NativeTestSuiteBinaryRenderer.class);
+        registration.add(NativePlatforms.class);
+        registration.add(NativePlatformResolver.class);
     }
 
     public void registerBuildServices(ServiceRegistration registration) {
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/platform/internal/NativePlatforms.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/platform/internal/NativePlatforms.java
index 7ec3e19..f4b21eb 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/platform/internal/NativePlatforms.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/platform/internal/NativePlatforms.java
@@ -28,7 +28,7 @@ public class NativePlatforms {
     private static final String ARCH_X86 = "x86";
 
     // TODO:DAZ This config should be used via platforms.create {}
-    public static Set<DefaultNativePlatform> defaultPlatformDefinitions() {
+    public Set<DefaultNativePlatform> defaultPlatformDefinitions() {
         Set<DefaultNativePlatform> platforms = new LinkedHashSet<DefaultNativePlatform>();
 
         OperatingSystemInternal windows = new DefaultOperatingSystem(OS_WINDOWS);
@@ -91,7 +91,7 @@ public class NativePlatforms {
         return String.format("%s_%s", os, arch);
     }
 
-    public static String getDefaultPlatformName() {
+    public String getDefaultPlatformName() {
         NativePlatformInternal defaultPlatform = new DefaultNativePlatform("default");
         OperatingSystemInternal os = defaultPlatform.getOperatingSystem();
         ArchitectureInternal architecture = defaultPlatform.getArchitecture();
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPlugin.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPlugin.java
index 855f2fb..8884d95 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPlugin.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPlugin.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.nativeplatform.plugins;
 
-import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.*;
 import org.gradle.api.artifacts.repositories.ArtifactRepository;
@@ -23,38 +22,35 @@ import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.tasks.TaskContainer;
-import org.gradle.internal.Actions;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.language.base.internal.LanguageSourceSetInternal;
 import org.gradle.language.base.internal.SourceTransformTaskConfig;
-import org.gradle.language.base.internal.registry.LanguageTransform;
 import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.base.plugins.ComponentModelBasePlugin;
 import org.gradle.language.nativeplatform.DependentSourceSet;
 import org.gradle.language.nativeplatform.HeaderExportingSourceSet;
+import org.gradle.language.nativeplatform.internal.DependentSourceSetInternal;
 import org.gradle.model.*;
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
 import org.gradle.nativeplatform.*;
 import org.gradle.nativeplatform.internal.*;
-import org.gradle.nativeplatform.internal.configure.*;
-import org.gradle.nativeplatform.internal.pch.DefaultPreCompiledHeaderTransformContainer;
-import org.gradle.nativeplatform.internal.pch.PreCompiledHeaderTransformContainer;
+import org.gradle.nativeplatform.internal.configure.NativeComponentRules;
+import org.gradle.nativeplatform.internal.pch.PchEnabledLanguageTransform;
 import org.gradle.nativeplatform.internal.prebuilt.DefaultPrebuiltLibraries;
 import org.gradle.nativeplatform.internal.prebuilt.PrebuiltLibraryInitializer;
-import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
 import org.gradle.nativeplatform.platform.NativePlatform;
 import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform;
+import org.gradle.nativeplatform.platform.internal.NativePlatforms;
 import org.gradle.nativeplatform.tasks.PrefixHeaderFileGenerateTask;
 import org.gradle.nativeplatform.toolchain.internal.DefaultNativeToolChainRegistry;
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainRegistryInternal;
+import org.gradle.nativeplatform.toolchain.internal.PreCompiledHeader;
 import org.gradle.platform.base.*;
-import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder;
-import org.gradle.platform.base.internal.DefaultBinaryNamingSchemeBuilder;
 import org.gradle.platform.base.internal.PlatformResolvers;
 
 import javax.inject.Inject;
@@ -66,9 +62,11 @@ import java.io.File;
 @Incubating
 public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
     private final Instantiator instantiator;
+    private final ModelRegistry modelRegistry;
 
     @Inject
-    public NativeComponentModelPlugin(Instantiator instantiator) {
+    public NativeComponentModelPlugin(ModelRegistry modelRegistry, Instantiator instantiator) {
+        this.modelRegistry = modelRegistry;
         this.instantiator = instantiator;
     }
 
@@ -78,11 +76,11 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
         project.getExtensions().create("buildTypes", DefaultBuildTypeContainer.class, instantiator);
         project.getExtensions().create("flavors", DefaultFlavorContainer.class, instantiator);
         project.getExtensions().create("toolChains", DefaultNativeToolChainRegistry.class, instantiator);
+
+        modelRegistry.getRoot().applyToAllLinksTransitive(ModelType.of(NativeComponentSpec.class), NativeComponentRules.class);
     }
 
-    @SuppressWarnings("UnusedDeclaration")
     static class Rules extends RuleSource {
-
         @ComponentType
         void nativeExecutable(ComponentTypeBuilder<NativeExecutableSpec> builder) {
             builder.defaultImplementation(DefaultNativeExecutableSpec.class);
@@ -97,7 +95,8 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
         Repositories repositories(ServiceRegistry serviceRegistry, FlavorContainer flavors, PlatformContainer platforms, BuildTypeContainer buildTypes) {
             Instantiator instantiator = serviceRegistry.get(Instantiator.class);
             FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
-            Action<PrebuiltLibrary> initializer = new PrebuiltLibraryInitializer(instantiator, platforms.withType(NativePlatform.class), buildTypes, flavors);
+            NativePlatforms nativePlatforms = serviceRegistry.get(NativePlatforms.class);
+            Action<PrebuiltLibrary> initializer = new PrebuiltLibraryInitializer(instantiator, nativePlatforms, platforms.withType(NativePlatform.class), buildTypes, flavors);
             return new DefaultRepositories(instantiator, fileResolver, initializer);
         }
 
@@ -116,19 +115,9 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
             return extensionContainer.getByType(FlavorContainer.class);
         }
 
-        @Model
-        NamedDomainObjectSet<NativeComponentSpec> nativeComponents(ComponentSpecContainer components) {
-            return components.withType(NativeComponentSpec.class);
-        }
-
-        @Model
-        PreCompiledHeaderTransformContainer preCompiledHeaderTransformContainer(ServiceRegistry serviceRegistry) {
-            return serviceRegistry.get(Instantiator.class).newInstance(DefaultPreCompiledHeaderTransformContainer.class);
-        }
-
         @Mutate
-        public void registerNativePlatformResolver(PlatformResolvers resolvers) {
-            resolvers.register(new NativePlatformResolver());
+        public void registerNativePlatformResolver(PlatformResolvers resolvers, ServiceRegistry serviceRegistry) {
+            resolvers.register(serviceRegistry.get(NativePlatformResolver.class));
         }
 
         @Defaults
@@ -146,26 +135,19 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
             platforms.registerFactory(Platform.class, nativePlatformFactory);
         }
 
-        // TODO:DAZ Migrate to @BinaryType and @ComponentBinaries
-        @Mutate
-        public void createNativeBinaries(BinaryContainer binaries, NamedDomainObjectSet<NativeComponentSpec> nativeComponents,
-                                         LanguageTransformContainer languageTransforms, NativeToolChainRegistryInternal toolChains,
-                                         PlatformResolvers platforms, BuildTypeContainer buildTypes, FlavorContainer flavors,
-                                         ServiceRegistry serviceRegistry, @Path("buildDir") File buildDir, ITaskFactory taskFactory) {
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            NativeDependencyResolver resolver = serviceRegistry.get(NativeDependencyResolver.class);
-            Action<NativeBinarySpec> configureBinaryAction = new NativeBinarySpecInitializer(buildDir);
-            Action<NativeBinarySpec> setToolsAction = new ToolSettingNativeBinaryInitializer(languageTransforms);
-            @SuppressWarnings("unchecked") Action<NativeBinarySpec> initAction = Actions.composite(configureBinaryAction, setToolsAction);
-            NativeBinariesFactory factory = new DefaultNativeBinariesFactory(instantiator, initAction, resolver, taskFactory);
-            BinaryNamingSchemeBuilder namingSchemeBuilder = new DefaultBinaryNamingSchemeBuilder();
-            Action<NativeComponentSpec> createBinariesAction =
-                    new NativeComponentSpecInitializer(factory, namingSchemeBuilder, toolChains, platforms, buildTypes, flavors);
-
-            for (NativeComponentSpec component : nativeComponents) {
-                createBinariesAction.execute(component);
-                binaries.addAll(component.getBinaries());
-            }
+        @BinaryType
+        void registerSharedLibraryBinaryType(BinaryTypeBuilder<SharedLibraryBinarySpec> builder) {
+            builder.defaultImplementation(DefaultSharedLibraryBinarySpec.class);
+        }
+
+        @BinaryType
+        void registerStaticLibraryBinaryType(BinaryTypeBuilder<StaticLibraryBinarySpec> builder) {
+            builder.defaultImplementation(DefaultStaticLibraryBinarySpec.class);
+        }
+
+        @BinaryType
+        void registerNativeExecutableBinaryType(BinaryTypeBuilder<NativeExecutableBinarySpec> builder) {
+            builder.defaultImplementation(DefaultNativeExecutableBinarySpec.class);
         }
 
         @Finalize
@@ -190,79 +172,86 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
         }
 
         @Mutate
-        void configureGeneratedSourceSets(CollectionBuilder<ComponentSpec> componentSpecs) {
-            componentSpecs.afterEach(new Action<ComponentSpec>() {
+        void configureGeneratedSourceSets(ModelMap<NativeComponentSpec> componentSpecs) {
+            componentSpecs.afterEach(new Action<NativeComponentSpec>() {
                 @Override
-                public void execute(ComponentSpec componentSpec) {
-                    for (LanguageSourceSetInternal languageSourceSet : componentSpec.getSource().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");
+                public void execute(NativeComponentSpec componentSpec) {
+                    componentSpec.getSource().withType(LanguageSourceSet.class).afterEach(new Action<LanguageSourceSet>() {
+                        @Override
+                        public void execute(LanguageSourceSet languageSourceSet) {
+                            LanguageSourceSetInternal internalSourceSet = (LanguageSourceSetInternal) languageSourceSet;
+                            Task generatorTask = internalSourceSet.getGeneratorTask();
+                            if (generatorTask != null) {
+                                languageSourceSet.builtBy(generatorTask);
+                                maybeSetSourceDir(languageSourceSet.getSource(), generatorTask, "sourceDir");
+                                if (languageSourceSet instanceof HeaderExportingSourceSet) {
+                                    maybeSetSourceDir(((HeaderExportingSourceSet) languageSourceSet).getExportedHeaders(), generatorTask, "headerDir");
+                                }
                             }
                         }
-                    }
+                    });
                 }
             });
         }
 
         @Mutate
-        void configurePrefixHeaderFiles(CollectionBuilder<ComponentSpec> componentSpecs, final @Path("buildDir") File buildDir) {
-            componentSpecs.afterEach(new Action<ComponentSpec>() {
+        void configurePrefixHeaderFiles(ModelMap<NativeComponentSpec> componentSpecs, final @Path("buildDir") File buildDir) {
+            componentSpecs.afterEach(new Action<NativeComponentSpec>() {
                 @Override
-                public void execute(ComponentSpec componentSpec) {
-                    for (DependentSourceSet dependentSourceSet : componentSpec.getSource().withType(DependentSourceSet.class)) {
-                        if (CollectionUtils.isNotEmpty(dependentSourceSet.getPreCompiledHeaders())) {
-                            String prefixHeaderDirName = String.format("tmp/%s/prefixHeaders", dependentSourceSet.getName());
-                            File prefixHeaderDir = new File(buildDir, prefixHeaderDirName);
-                            final File prefixHeaderFile = new File(prefixHeaderDir, "prefix-headers.h");
-                            dependentSourceSet.setPrefixHeaderFile(prefixHeaderFile);
+                public void execute(final NativeComponentSpec componentSpec) {
+                    componentSpec.getSource().withType(DependentSourceSet.class).afterEach(new Action<DependentSourceSet>() {
+                        @Override
+                        public void execute(DependentSourceSet dependentSourceSet) {
+                            if (dependentSourceSet.getPreCompiledHeader() != null) {
+                                DependentSourceSetInternal internalSourceSet = (DependentSourceSetInternal) dependentSourceSet;
+                                String prefixHeaderDirName = String.format("tmp/%s/%s/prefixHeaders", componentSpec.getName(), dependentSourceSet.getName());
+                                File prefixHeaderDir = new File(buildDir, prefixHeaderDirName);
+                                File prefixHeaderFile = new File(prefixHeaderDir, "prefix-headers.h");
+                                internalSourceSet.setPrefixHeaderFile(prefixHeaderFile);
+                            }
                         }
-                    }
+                    });
                 }
             });
         }
 
         @Mutate
-        void configurePrefixHeaderGenerationTasks(final TaskContainer tasks, NamedDomainObjectSet<NativeComponentSpec> nativeComponents) {
-            for (NativeComponentSpec nativeComponentSpec : nativeComponents) {
-                nativeComponentSpec.getSource().withType(DependentSourceSet.class, new Action<DependentSourceSet>() {
-                    @Override
-                    public void execute(final DependentSourceSet dependentSourceSet) {
-                        if (dependentSourceSet.getPrefixHeaderFile() !=  null) {
-                            String taskName = String.format("generate%sPrefixHeaderFile", StringUtils.capitalize(dependentSourceSet.getName()));
-                            tasks.create(taskName, PrefixHeaderFileGenerateTask.class, new Action<PrefixHeaderFileGenerateTask>() {
-                                @Override
-                                public void execute(PrefixHeaderFileGenerateTask prefixHeaderFileGenerateTask) {
-                                    prefixHeaderFileGenerateTask.setPrefixHeaderFile(dependentSourceSet.getPrefixHeaderFile());
-                                    prefixHeaderFileGenerateTask.setHeaders(dependentSourceSet.getPreCompiledHeaders());
-                                }
-                            });
-                        }
+        void configurePrefixHeaderGenerationTasks(final TaskContainer tasks, ModelMap<NativeComponentSpec> nativeComponents) {
+            for (final NativeComponentSpec nativeComponentSpec : nativeComponents.values()) {
+                for (final DependentSourceSet dependentSourceSet : nativeComponentSpec.getSource().withType(DependentSourceSet.class).values()) {
+                    final DependentSourceSetInternal internalSourceSet = (DependentSourceSetInternal) dependentSourceSet;
+                    if (internalSourceSet.getPrefixHeaderFile() != null) {
+                        String taskName = String.format("generate%s%sPrefixHeaderFile", StringUtils.capitalize(nativeComponentSpec.getName()), StringUtils.capitalize(dependentSourceSet.getName()));
+                        tasks.create(taskName, PrefixHeaderFileGenerateTask.class, new Action<PrefixHeaderFileGenerateTask>() {
+                            @Override
+                            public void execute(PrefixHeaderFileGenerateTask prefixHeaderFileGenerateTask) {
+                                prefixHeaderFileGenerateTask.setPrefixHeaderFile(internalSourceSet.getPrefixHeaderFile());
+                                prefixHeaderFileGenerateTask.setHeader(dependentSourceSet.getPreCompiledHeader());
+                            }
+                        });
                     }
-                });
+                }
             }
         }
 
         @Mutate
-        void configurePreCompiledHeaderCompileTasks(CollectionBuilder<NativeBinarySpecInternal> binaries, final ServiceRegistry serviceRegistry, final PreCompiledHeaderTransformContainer pchTransformContainer, final @Path("buildDir") File buildDir) {
+        void configurePreCompiledHeaderCompileTasks(ModelMap<NativeBinarySpecInternal> binaries, final ServiceRegistry serviceRegistry, final LanguageTransformContainer languageTransforms, final @Path("buildDir") File buildDir) {
             binaries.all(new Action<NativeBinarySpecInternal>() {
                 @Override
                 public void execute(final NativeBinarySpecInternal nativeBinarySpec) {
-                    for (final LanguageTransform<?, ?> transform : pchTransformContainer) {
+                    for (final PchEnabledLanguageTransform<?> transform : languageTransforms.withType(PchEnabledLanguageTransform.class)) {
                         nativeBinarySpec.getSource().withType(transform.getSourceSetType(), new Action<LanguageSourceSet>() {
                             @Override
                             public void execute(final LanguageSourceSet languageSourceSet) {
                                 final DependentSourceSet dependentSourceSet = (DependentSourceSet) languageSourceSet;
-                                if (CollectionUtils.isNotEmpty(dependentSourceSet.getPreCompiledHeaders())) {
-                                    final SourceTransformTaskConfig taskConfig = transform.getTransformTask();
-                                    String pchTaskName = String.format("%s%s%sPreCompiledHeader", taskConfig.getTaskPrefix(), StringUtils.capitalize(nativeBinarySpec.getName()), StringUtils.capitalize(dependentSourceSet.getName()));
-                                    nativeBinarySpec.getTasks().create(pchTaskName, taskConfig.getTaskType(), new Action<DefaultTask>() {
+                                if (dependentSourceSet.getPreCompiledHeader() != null) {
+                                    nativeBinarySpec.getPrefixFileToPCH().put(((DependentSourceSetInternal) dependentSourceSet).getPrefixHeaderFile(), new PreCompiledHeader());
+                                    final SourceTransformTaskConfig pchTransformTaskConfig = transform.getPchTransformTask();
+                                    String pchTaskName = String.format("%s%s%sPreCompiledHeader", pchTransformTaskConfig.getTaskPrefix(), StringUtils.capitalize(nativeBinarySpec.getName()), StringUtils.capitalize(dependentSourceSet.getName()));
+                                    nativeBinarySpec.getTasks().create(pchTaskName, pchTransformTaskConfig.getTaskType(), new Action<DefaultTask>() {
                                         @Override
                                         public void execute(DefaultTask task) {
-                                            taskConfig.configureTask(task, nativeBinarySpec, dependentSourceSet);
+                                            pchTransformTaskConfig.configureTask(task, nativeBinarySpec, dependentSourceSet);
                                         }
                                     });
                                 }
@@ -273,31 +262,13 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
             });
         }
 
-        @Mutate
-        public void applyHeaderSourceSetConventions(CollectionBuilder<ComponentSpec> componentSpecs) {
-            componentSpecs.afterEach(new Action<ComponentSpec>() {
-                @Override
-                public void execute(ComponentSpec componentSpec) {
-                    DomainObjectSet<LanguageSourceSet> functionalSourceSet = componentSpec.getSource();
-                    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", componentSpec.getName()));
-                        }
-
-                        headerSourceSet.getImplicitHeaders().setSrcDirs(headerSourceSet.getSource().getSrcDirs());
-                        headerSourceSet.getImplicitHeaders().include("**/*.h");
-                    }
-                }
-            });
-        }
-
         private void maybeSetSourceDir(SourceDirectorySet sourceSet, Task task, String propertyName) {
             Object value = task.property(propertyName);
             if (value != null) {
                 sourceSet.srcDir(value);
             }
         }
+
     }
 
     private static class DefaultRepositories extends DefaultPolymorphicDomainObjectContainer<ArtifactRepository> implements Repositories {
@@ -316,4 +287,5 @@ public class NativeComponentModelPlugin implements Plugin<ProjectInternal> {
             return object.getName();
         }
     }
-}
\ No newline at end of file
+
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/tasks/PrefixHeaderFileGenerateTask.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/tasks/PrefixHeaderFileGenerateTask.java
index 905a73b..cbd4184 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/tasks/PrefixHeaderFileGenerateTask.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/tasks/PrefixHeaderFileGenerateTask.java
@@ -16,36 +16,36 @@
 
 package org.gradle.nativeplatform.tasks;
 
+import com.google.common.collect.Lists;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.nativeplatform.toolchain.internal.PrefixHeaderFileGeneratorUtil;
+import org.gradle.nativeplatform.toolchain.internal.PCHUtils;
 
 import java.io.File;
-import java.util.Set;
 
 /**
  * Generates a prefix header file from a list of headers to be precompiled.
  */
 public class PrefixHeaderFileGenerateTask extends DefaultTask {
     @Input
-    Set<String> headers;
+    String header;
 
     @OutputFile
     File prefixHeaderFile;
 
     @TaskAction
     void generatePrefixHeaderFile() {
-        PrefixHeaderFileGeneratorUtil.generatePCHFile(headers, prefixHeaderFile);
+        PCHUtils.generatePCHFile(Lists.newArrayList(header), prefixHeaderFile);
     }
 
-    public Set<String> getHeaders() {
-        return headers;
+    public String getHeaders() {
+        return header;
     }
 
-    public void setHeaders(Set<String> headers) {
-        this.headers = headers;
+    public void setHeader(String header) {
+        this.header = header;
     }
 
     public File getPrefixHeaderFile() {
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/test/plugins/NativeBinariesTestPlugin.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/test/plugins/NativeBinariesTestPlugin.java
index 3ed9075..8812d14 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/test/plugins/NativeBinariesTestPlugin.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/test/plugins/NativeBinariesTestPlugin.java
@@ -17,13 +17,23 @@
 package org.gradle.nativeplatform.test.plugins;
 
 import org.gradle.api.*;
+import org.gradle.api.internal.rules.ModelMapCreators;
 import org.gradle.api.tasks.TaskContainer;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.plugins.LifecycleBasePlugin;
 import org.gradle.language.nativeplatform.DependentSourceSet;
-import org.gradle.model.*;
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.Finalize;
+import org.gradle.model.ModelMap;
+import org.gradle.model.Mutate;
+import org.gradle.model.RuleSource;
+import org.gradle.model.internal.core.ModelCreator;
+import org.gradle.model.internal.core.ModelPath;
+import org.gradle.model.internal.core.ModelReference;
+import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
+import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
+import org.gradle.model.internal.manage.schema.ModelMapSchema;
+import org.gradle.model.internal.manage.schema.ModelSchemaStore;
+import org.gradle.model.internal.registry.ModelRegistry;
+import org.gradle.model.internal.type.ModelType;
 import org.gradle.nativeplatform.NativeBinarySpec;
 import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
 import org.gradle.nativeplatform.plugins.NativeComponentPlugin;
@@ -32,28 +42,39 @@ import org.gradle.nativeplatform.test.NativeTestSuiteBinarySpec;
 import org.gradle.nativeplatform.test.tasks.RunTestExecutable;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
-import org.gradle.platform.base.internal.test.DefaultTestSuiteContainer;
+import org.gradle.platform.base.internal.ComponentSpecFactory;
 import org.gradle.platform.base.test.TestSuiteContainer;
+import org.gradle.platform.base.test.TestSuiteSpec;
 
+import javax.inject.Inject;
 import java.io.File;
 
 /**
- * A plugin that sets up the infrastructure for testing native binaries with CUnit.
+ * A plugin that sets up the infrastructure for testing native binaries.
  */
 @Incubating
 public class NativeBinariesTestPlugin implements Plugin<Project> {
+    private final ModelRegistry modelRegistry;
+    private final ModelSchemaStore schemaStore;
+
+    @Inject
+    public NativeBinariesTestPlugin(ModelRegistry modelRegistry, ModelSchemaStore schemaStore) {
+        this.modelRegistry = modelRegistry;
+        this.schemaStore = schemaStore;
+    }
+
     public void apply(final Project project) {
         project.getPluginManager().apply(NativeComponentPlugin.class);
+
+        ModelRuleDescriptor descriptor = new SimpleModelRuleDescriptor(NativeBinariesTestPlugin.class.getName() + ".apply()");
+        ModelMapSchema<TestSuiteContainer> schema = (ModelMapSchema<TestSuiteContainer>) schemaStore.getSchema(ModelType.of(TestSuiteContainer.class));
+        ModelCreator testSuitesCreator = ModelMapCreators.specialized(ModelPath.path("testSuites"), TestSuiteSpec.class, TestSuiteContainer.class, schema.getManagedImpl().asSubclass(TestSuiteContainer.class), ModelReference.of(ComponentSpecFactory.class), descriptor);
+
+        modelRegistry.create(testSuitesCreator);
     }
 
     @SuppressWarnings("UnusedDeclaration")
     static class Rules extends RuleSource {
-        @Model
-        TestSuiteContainer testSuites(ServiceRegistry serviceRegistry) {
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            return instantiator.newInstance(DefaultTestSuiteContainer.class, instantiator);
-        }
-
         @Finalize
             // Must run after test binaries have been created (currently in CUnit plugin)
         void attachTestedBinarySourcesToTestBinaries(BinaryContainer binaries) {
@@ -87,7 +108,14 @@ public class NativeBinariesTestPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void attachBinariesToCheckLifecycle(CollectionBuilder<Task> tasks, final BinaryContainer binaries) {
+        public void copyTestBinariesToGlobalContainer(BinaryContainer binaries, TestSuiteContainer testSuites) {
+            for (final TestSuiteSpec testSuite : testSuites.withType(TestSuiteSpec.class).values()) {
+                binaries.addAll(testSuite.getBinaries().values());
+            }
+        }
+
+        @Mutate
+        void attachBinariesToCheckLifecycle(ModelMap<Task> tasks, final BinaryContainer binaries) {
             // TODO - binaries aren't an input to this rule, they're an input to the action
             tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME, new Action<Task>() {
                 @Override
@@ -99,4 +127,5 @@ public class NativeBinariesTestPlugin implements Plugin<Project> {
             });
         }
     }
+
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompileSpec.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompileSpec.java
index 423f13c..1e044ad 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompileSpec.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompileSpec.java
@@ -23,7 +23,6 @@ import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * A compile spec that will be used to generate object files for combining into a native binary.
@@ -75,9 +74,9 @@ public interface NativeCompileSpec extends BinaryToolSpec {
 
     void setPreCompiledHeaderObjectFile(File preCompiledHeaderObjectFile);
 
-    Set<String> getPreCompiledHeaders();
+    String getPreCompiledHeader();
 
-    void setPreCompiledHeaders(Set<String> headers);
+    void setPreCompiledHeader(String header);
 
     Map<File, SourceIncludes> getSourceFileIncludes();
 
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompiler.java
index 00a302f..f87aa08 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompiler.java
@@ -21,6 +21,9 @@ import com.google.common.collect.Lists;
 import org.gradle.api.Action;
 import org.gradle.api.Transformer;
 import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.FileUtils;
@@ -28,6 +31,7 @@ import org.gradle.internal.operations.BuildOperationProcessor;
 import org.gradle.internal.operations.BuildOperationQueue;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.language.base.internal.compile.Compiler;
+import org.gradle.language.nativeplatform.internal.Include;
 import org.gradle.language.nativeplatform.internal.SourceIncludes;
 import org.gradle.nativeplatform.internal.CompilerOutputFileNamingScheme;
 import org.gradle.util.CollectionUtils;
@@ -44,6 +48,7 @@ abstract public class NativeCompiler<T extends NativeCompileSpec> implements Com
     private final CommandLineToolContext invocationContext;
     private final String objectFileExtension;
     private final boolean useCommandFile;
+    private final Logger logger = Logging.getLogger(NativeCompiler.class);
 
     private final BuildOperationProcessor buildOperationProcessor;
 
@@ -94,7 +99,7 @@ abstract public class NativeCompiler<T extends NativeCompileSpec> implements Com
         return Collections.singletonList(sourceFile.getAbsolutePath());
     }
 
-    protected abstract List<String> getOutputArgs(T spec, File outputFile);
+    protected abstract List<String> getOutputArgs(File outputFile);
 
     protected abstract void addOptionsFileArgs(List<String> args, File tempDir);
 
@@ -115,35 +120,44 @@ abstract public class NativeCompiler<T extends NativeCompileSpec> implements Com
     }
 
     protected List<String> maybeGetPCHArgs(final T spec, File sourceFile) {
-        if (spec.getPreCompiledHeaders() == null) {
+        if (spec.getPreCompiledHeader() == null) {
             return Lists.newArrayList();
         }
 
         final SourceIncludes includes = spec.getSourceFileIncludes().get(sourceFile);
-        boolean usePCH = CollectionUtils.every(spec.getPreCompiledHeaders(), new Spec<String>() {
-            @Override
-            public boolean isSatisfiedBy(String header) {
-                List<String> headerIncludes;
-                if (header.startsWith("<")) {
-                    header = header.substring(1, header.length()-1);
-                    headerIncludes = includes.getSystemIncludes();
-                } else {
-                    headerIncludes = includes.getQuotedIncludes();
-                }
-                return headerIncludes.contains(header);
-            }
-        });
+        final String header = spec.getPreCompiledHeader();
+
+        List<Include> headers = includes.getIncludesAndImports();
+        boolean usePCH = !headers.isEmpty() && header.equals(headers.get(0).getValue());
+
         if (usePCH) {
             return getPCHArgs(spec);
         } else {
+            boolean containsHeader = CollectionUtils.any(headers, new Spec<Include>() {
+                @Override
+                public boolean isSatisfiedBy(Include include) {
+                    return include.getValue().equals(header);
+                }
+            });
+            if (containsHeader) {
+                logger.log(LogLevel.WARN, getCantUsePCHMessage(spec.getPreCompiledHeader(), sourceFile));
+            }
             return Lists.newArrayList();
         }
     }
 
+    private static String getCantUsePCHMessage(String pchHeader, File sourceFile) {
+        return "The source file "
+                .concat(sourceFile.getName())
+                .concat(" includes the header ")
+                .concat(pchHeader)
+                .concat(" but it is not the first declared header, so the pre-compiled header will not be used.");
+    }
+
     protected CommandLineToolInvocation createPerFileInvocation(List<String> genericArgs, File sourceFile, File objectDir, T spec) {
         String objectFileSuffix = objectFileExtension;
         List<String> sourceArgs = getSourceArgs(sourceFile);
-        List<String> outputArgs = getOutputArgs(spec, getOutputFileDir(sourceFile, objectDir, objectFileSuffix));
+        List<String> outputArgs = getOutputArgs(getOutputFileDir(sourceFile, objectDir, objectFileSuffix));
         List<String> pchArgs = maybeGetPCHArgs(spec, sourceFile);
 
         return invocationContext.createInvocation("compiling ".concat(sourceFile.getName()), objectDir, buildPerFileArgs(genericArgs, sourceArgs, outputArgs, pchArgs), spec.getOperationLogger());
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHObjectDirectoryGeneratorUtil.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHObjectDirectoryGeneratorUtil.java
deleted file mode 100644
index 0ef195a..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHObjectDirectoryGeneratorUtil.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.nativeplatform.toolchain.internal;
-
-import org.apache.commons.io.FileUtils;
-import org.gradle.api.UncheckedIOException;
-
-import java.io.File;
-import java.io.IOException;
-
-public class PCHObjectDirectoryGeneratorUtil {
-    public static File generatePCHObjectDirectory(File tempDir, File prefixHeaderFile, File preCompiledHeaderObjectFile) {
-        File generatedDir = new File(tempDir, "preCompiledHeaders");
-        generatedDir.mkdirs();
-        File generatedHeader = new File(generatedDir, prefixHeaderFile.getName());
-        File generatedPCH = new File(generatedDir, preCompiledHeaderObjectFile.getName());
-        try {
-            FileUtils.copyFile(prefixHeaderFile, generatedHeader);
-            FileUtils.copyFile(preCompiledHeaderObjectFile, generatedPCH);
-            return generatedDir;
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtils.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtils.java
new file mode 100644
index 0000000..c58c845
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtils.java
@@ -0,0 +1,108 @@
+/*
+ * 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.nativeplatform.toolchain.internal;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec;
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class PCHUtils {
+    public static File generatePCHObjectDirectory(File tempDir, File prefixHeaderFile, File preCompiledHeaderObjectFile) {
+        File generatedDir = new File(tempDir, "preCompiledHeaders");
+        generatedDir.mkdirs();
+        File generatedHeader = new File(generatedDir, prefixHeaderFile.getName());
+        File generatedPCH = new File(generatedDir, preCompiledHeaderObjectFile.getName());
+        try {
+            FileUtils.copyFile(prefixHeaderFile, generatedHeader);
+            FileUtils.copyFile(preCompiledHeaderObjectFile, generatedPCH);
+            return generatedDir;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static void generatePCHFile(List<String> headers, File headerFile) {
+        if (!headerFile.getParentFile().exists()) {
+            headerFile.getParentFile().mkdirs();
+        }
+
+        try {
+            FileUtils.writeLines(headerFile, CollectionUtils.collect(headers, new Transformer<String, String>() {
+                @Override
+                public String transform(String header) {
+                    if (header.startsWith("<")) {
+                        return "#include ".concat(header);
+                    } else {
+                        return "#include \"".concat(header).concat("\"");
+                    }
+                }
+            }));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static <T extends NativeCompileSpec> File generatePCHSourceFile(T original, File sourceFile) {
+        File generatedSourceDir = new File(original.getTempDir(), "pchGenerated");
+        generatedSourceDir.mkdirs();
+        File generatedSource = new File(generatedSourceDir, FilenameUtils.removeExtension(sourceFile.getName()).concat(getSourceFileExtension(original.getClass())));
+        File headerFileCopy = new File(generatedSourceDir, sourceFile.getName());
+        try {
+            FileUtils.copyFile(sourceFile, headerFileCopy);
+            FileUtils.writeStringToFile(generatedSource, "#include \"".concat(headerFileCopy.getName()).concat("\""));
+            return generatedSource;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static <T extends NativeCompileSpec> Transformer<T, T> getHeaderToSourceFileTransformer(Class<T> type) {
+        return new Transformer<T, T>() {
+            @Override
+            public T transform(T original) {
+                List<File> newSourceFiles = Lists.newArrayList();
+                for (File sourceFile : original.getSourceFiles()) {
+                    newSourceFiles.add(generatePCHSourceFile(original, sourceFile));
+                }
+                original.setSourceFiles(newSourceFiles);
+                return original;
+            }
+        };
+    }
+
+    private static String getSourceFileExtension(Class<? extends NativeCompileSpec> specClass) {
+        if (CPCHCompileSpec.class.isAssignableFrom(specClass)) {
+            return ".c";
+        }
+
+        if (CppPCHCompileSpec.class.isAssignableFrom(specClass)) {
+            return ".cpp";
+        }
+
+        throw new IllegalArgumentException("Cannot determine source file extension for spec with type ".concat(specClass.getSimpleName()));
+
+    }
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PlatformToolProvider.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PlatformToolProvider.java
index 53f8342..ead81f4 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PlatformToolProvider.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PlatformToolProvider.java
@@ -21,8 +21,6 @@ import org.gradle.platform.base.internal.toolchain.ToolProvider;
 public interface PlatformToolProvider extends ToolProvider {
     String getObjectFileExtension();
 
-    String getPCHFileExtension();
-
     String getExecutableName(String executablePath);
 
     String getSharedLibraryName(String libraryPath);
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PreCompiledHeader.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PreCompiledHeader.java
new file mode 100644
index 0000000..bf93cfc
--- /dev/null
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PreCompiledHeader.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractBuildableModelElement;
+import org.gradle.api.tasks.InputFiles;
+
+import java.io.File;
+
+public class PreCompiledHeader extends AbstractBuildableModelElement {
+    FileCollection pchObjects;
+    File prefixHeaderFile;
+    String includeString;
+
+    public File getObjectFile() {
+        return pchObjects == null ? null : pchObjects.getSingleFile();
+    }
+
+    public void setPchObjects(FileCollection pchObjects) {
+        this.pchObjects = pchObjects;
+    }
+
+    @InputFiles
+    public FileCollection getPchObjects() {
+        return pchObjects;
+    }
+
+    public File getPrefixHeaderFile() {
+        return prefixHeaderFile;
+    }
+
+    public void setPrefixHeaderFile(File prefixHeaderFile) {
+        this.prefixHeaderFile = prefixHeaderFile;
+    }
+
+    public String getIncludeString() {
+        return includeString;
+    }
+
+    public void setIncludeString(String includeString) {
+        this.includeString = includeString;
+    }
+}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtil.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtil.java
deleted file mode 100644
index 8591e7b..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtil.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal;
-
-import org.apache.commons.io.FileUtils;
-import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.util.CollectionUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-public class PrefixHeaderFileGeneratorUtil {
-    public static void generatePCHFile(Set<String> headers, File headerFile) {
-        if (!headerFile.getParentFile().exists()) {
-            headerFile.getParentFile().mkdirs();
-        }
-
-        try {
-            FileUtils.writeLines(headerFile, CollectionUtils.collect(CollectionUtils.toList(headers), new Transformer<String, String>() {
-                @Override
-                public String transform(String header) {
-                    if (header.startsWith("<")) {
-                        return "#include ".concat(header);
-                    } else {
-                        return "#include \"".concat(header).concat("\"");
-                    }
-                }
-            }));
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/UnavailablePlatformToolProvider.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/UnavailablePlatformToolProvider.java
index c489330..d7fba22 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/UnavailablePlatformToolProvider.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/UnavailablePlatformToolProvider.java
@@ -49,12 +49,7 @@ public class UnavailablePlatformToolProvider implements PlatformToolProvider {
 
     @Override
     public String getObjectFileExtension() {
-        return null;
-    }
-
-    @Override
-    public String getPCHFileExtension() {
-        return null;
+        throw failure();
     }
 
     public String getExecutableName(String executablePath) {
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/Assembler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/Assembler.java
index a49300f..b91fce6 100755
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/Assembler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/Assembler.java
@@ -22,12 +22,22 @@ import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWor
 import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
 import org.gradle.nativeplatform.toolchain.internal.compilespec.AssembleSpec;
 
+import java.util.List;
+
 class Assembler extends GccCompatibleNativeCompiler<AssembleSpec> {
 
     Assembler(BuildOperationProcessor buildOperationProcessor, CommandLineToolInvocationWorker commandLineTool, CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
         super(buildOperationProcessor, commandLineTool, invocationContext, new AssemblerArgsTransformer(), Transformers.<AssembleSpec>noOpTransformer(), objectFileExtension, useCommandFile);
     }
 
+    @Override
+    protected Iterable<String> buildPerFileArgs(List<String> genericArgs, List<String> sourceArgs, List<String> outputArgs, List<String> pchArgs) {
+        if (pchArgs != null && !pchArgs.isEmpty()) {
+            throw new UnsupportedOperationException("Precompiled header arguments cannot be specified for an Assembler compiler.");
+        }
+        return super.buildPerFileArgs(genericArgs, sourceArgs, outputArgs, pchArgs);
+    }
+
     private static class AssemblerArgsTransformer  extends GccCompilerArgsTransformer<AssembleSpec> {
         protected String getLanguage() {
             return "assembler";
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompiler.java
index 9e43e4a..052ce13 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompiler.java
@@ -32,7 +32,7 @@ class GccCompatibleNativeCompiler<T extends NativeCompileSpec> extends NativeCom
     }
 
     @Override
-    protected List<String> getOutputArgs(T spec, File outputFile) {
+    protected List<String> getOutputArgs(File outputFile) {
         return Arrays.asList("-o", outputFile.getAbsolutePath());
     }
 
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java
index b06b164..f0e89b6 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java
@@ -132,7 +132,6 @@ class GccPlatformToolProvider extends AbstractPlatformToolProvider {
         return baseInvocation;
     }
 
-    @Override
     public String getPCHFileExtension() {
         return ".h.gch";
     }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/Assembler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/Assembler.java
index 77656ea..2002291 100755
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/Assembler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/Assembler.java
@@ -32,7 +32,10 @@ class Assembler extends VisualCppNativeCompiler<AssembleSpec> {
     }
 
     @Override
-    protected Iterable<String> buildPerFileArgs(List<String> genericArgs, List<String> sourceArgs, List<String> outputArgs, List<String> pchArgss) {
+    protected Iterable<String> buildPerFileArgs(List<String> genericArgs, List<String> sourceArgs, List<String> outputArgs, List<String> pchArgs) {
+        if (pchArgs != null && !pchArgs.isEmpty()) {
+            throw new UnsupportedOperationException("Precompiled header arguments cannot be specified for a Assembler compiler.");
+        }
         // ml/ml64 have position sensitive arguments,
         // e.g., /Fo must appear before /c and /c must appear before the source file.
 
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompiler.java
index c9b9ff2..243615c 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompiler.java
@@ -32,7 +32,7 @@ public class CPCHCompiler extends VisualCppNativeCompiler<CPCHCompileSpec> {
     }
 
     @Override
-    protected List<String> getOutputArgs(CPCHCompileSpec spec, File outputFile) {
+    protected List<String> getOutputArgs(File outputFile) {
         return Collections.singletonList("/Fp" + outputFile.getAbsolutePath());
     }
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompiler.java
index 9537efa..4aac28d 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompiler.java
@@ -32,7 +32,7 @@ public class CppPCHCompiler extends VisualCppNativeCompiler<CppPCHCompileSpec> {
     }
 
     @Override
-    protected List<String> getOutputArgs(CppPCHCompileSpec spec, File outputFile) {
+    protected List<String> getOutputArgs(File outputFile) {
         return Collections.singletonList("/Fp" + outputFile.getAbsolutePath());
     }
 }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompiler.java
index 5f3a017..d306e1e 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompiler.java
@@ -16,7 +16,6 @@
 
 package org.gradle.nativeplatform.toolchain.internal.msvcpp;
 
-import org.apache.commons.collections.CollectionUtils;
 import org.gradle.api.Transformer;
 import org.gradle.internal.operations.BuildOperationProcessor;
 import org.gradle.nativeplatform.toolchain.internal.*;
@@ -33,7 +32,7 @@ class VisualCppNativeCompiler<T extends NativeCompileSpec> extends NativeCompile
     }
 
     @Override
-    protected List<String> getOutputArgs(T spec, File outputFile) {
+    protected List<String> getOutputArgs(File outputFile) {
         // MSVC doesn't allow a space between Fo and the file name
         return Collections.singletonList("/Fo" + outputFile.getAbsolutePath());
     }
@@ -48,11 +47,9 @@ class VisualCppNativeCompiler<T extends NativeCompileSpec> extends NativeCompile
     @Override
     protected List<String> getPCHArgs(T spec) {
         List<String> pchArgs = new ArrayList<String>();
-        if (CollectionUtils.isNotEmpty(spec.getPreCompiledHeaders()) && spec.getPreCompiledHeaderObjectFile() != null) {
-            String lastHeader = (String) CollectionUtils.get(spec.getPreCompiledHeaders(), spec.getPreCompiledHeaders().size() - 1);
-            if (lastHeader.startsWith("<")) {
-                lastHeader = lastHeader.substring(1, lastHeader.length()-1);
-            }
+        if (spec.getPreCompiledHeader() != null && spec.getPreCompiledHeaderObjectFile() != null) {
+            String lastHeader = spec.getPreCompiledHeader();
+
             pchArgs.add("/Yu".concat(lastHeader));
             pchArgs.add("/Fp".concat(spec.getPreCompiledHeaderObjectFile().getAbsolutePath()));
         }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtil.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtil.java
deleted file mode 100644
index 3eb897b..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtil.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal.msvcpp;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec;
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec;
-
-import java.io.File;
-import java.io.IOException;
-
-public class VisualCppPCHSourceFileGeneratorUtil {
-    private static SourceFileExtensionCalculator calculator = new SourceFileExtensionCalculator();
-
-    public static <T extends NativeCompileSpec> File generatePCHSourceFile(T original, File sourceFile) {
-        File generatedSourceDir = new File(original.getTempDir(), "pchGeneratedSource");
-        generatedSourceDir.mkdirs();
-        File generatedSource = new File(generatedSourceDir, FilenameUtils.removeExtension(sourceFile.getName()).concat(calculator.transform(original.getClass())));
-        File headerFileCopy = new File(generatedSourceDir, sourceFile.getName());
-        try {
-            FileUtils.copyFile(sourceFile, headerFileCopy);
-            FileUtils.writeStringToFile(generatedSource, "#include \"".concat(headerFileCopy.getName()).concat("\""));
-            return generatedSource;
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    static class SourceFileExtensionCalculator implements Transformer<String, Class<? extends NativeCompileSpec>> {
-        @Override
-        public String transform(Class<? extends NativeCompileSpec> specClass) {
-            if (CPCHCompileSpec.class.isAssignableFrom(specClass)) {
-                return ".c";
-            }
-
-            if (CppPCHCompileSpec.class.isAssignableFrom(specClass)) {
-                return ".cpp";
-            }
-
-            throw new GradleException("Cannot determine source file extension for spec with type ".concat(specClass.getSimpleName()));
-        }
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformer.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformer.java
deleted file mode 100644
index 77a9c79..0000000
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformer.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal.msvcpp;
-
-import com.google.common.collect.Lists;
-import org.gradle.api.Transformer;
-import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
-
-import java.io.File;
-import java.util.List;
-
-public class VisualCppPCHSourceFileTransformer<T extends NativeCompileSpec> implements Transformer<T, T> {
-    @Override
-    public T transform(T original) {
-        List<File> newSourceFiles = Lists.newArrayList();
-        for (File sourceFile : original.getSourceFiles()) {
-            newSourceFiles.add(VisualCppPCHSourceFileGeneratorUtil.generatePCHSourceFile(original, sourceFile));
-        }
-        original.setSourceFiles(newSourceFiles);
-        return original;
-    }
-}
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPlatformToolProvider.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPlatformToolProvider.java
index 2bc2f35..216b209 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPlatformToolProvider.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPlatformToolProvider.java
@@ -70,8 +70,8 @@ class VisualCppPlatformToolProvider extends AbstractPlatformToolProvider {
 
     @Override
     protected Compiler<?> createCppPCHCompiler() {
-        CommandLineToolInvocationWorker commandLineTool = tool("C++ compiler", visualCpp.getCompiler(targetPlatform));
-        CppPCHCompiler cppPCHCompiler = new CppPCHCompiler(buildOperationProcessor, commandLineTool, context(commandLineToolConfigurations.get(ToolType.CPP_COMPILER)), allSpecTransforms(CppPCHCompileSpec.class), getPCHFileExtension(), true);
+        CommandLineToolInvocationWorker commandLineTool = tool("C++ PCH compiler", visualCpp.getCompiler(targetPlatform));
+        CppPCHCompiler cppPCHCompiler = new CppPCHCompiler(buildOperationProcessor, commandLineTool, context(commandLineToolConfigurations.get(ToolType.CPP_COMPILER)), pchSpecTransforms(CppPCHCompileSpec.class), getPCHFileExtension(), true);
         return new OutputCleaningCompiler<CppPCHCompileSpec>(cppPCHCompiler, getPCHFileExtension());
     }
 
@@ -84,8 +84,8 @@ class VisualCppPlatformToolProvider extends AbstractPlatformToolProvider {
 
     @Override
     protected Compiler<?> createCPCHCompiler() {
-        CommandLineToolInvocationWorker commandLineTool = tool("C compiler", visualCpp.getCompiler(targetPlatform));
-        CPCHCompiler cpchCompiler = new CPCHCompiler(buildOperationProcessor, commandLineTool, context(commandLineToolConfigurations.get(ToolType.C_COMPILER)), allSpecTransforms(CPCHCompileSpec.class), getPCHFileExtension(), true);
+        CommandLineToolInvocationWorker commandLineTool = tool("C PCH compiler", visualCpp.getCompiler(targetPlatform));
+        CPCHCompiler cpchCompiler = new CPCHCompiler(buildOperationProcessor, commandLineTool, context(commandLineToolConfigurations.get(ToolType.C_COMPILER)), pchSpecTransforms(CPCHCompileSpec.class), getPCHFileExtension(), true);
         return new OutputCleaningCompiler<CPCHCompileSpec>(cpchCompiler, getPCHFileExtension());
     }
 
@@ -154,12 +154,12 @@ class VisualCppPlatformToolProvider extends AbstractPlatformToolProvider {
         }
     }
 
-    private <T extends NativeCompileSpec> Transformer<T, T> allSpecTransforms(final Class<T> type) {
+    private <T extends NativeCompileSpec> Transformer<T, T> pchSpecTransforms(final Class<T> type) {
         return new Transformer<T, T>() {
             @Override
             public T transform(T original) {
                 List<Transformer<T, T>> transformers = Lists.newArrayList();
-                transformers.add(new VisualCppPCHSourceFileTransformer<T>());
+                transformers.add(PCHUtils.getHeaderToSourceFileTransformer(type));
                 transformers.add(addIncludePathAndDefinitions(type));
 
                 T next = original;
@@ -194,7 +194,6 @@ class VisualCppPlatformToolProvider extends AbstractPlatformToolProvider {
         };
     }
 
-    @Override
     public String getPCHFileExtension() {
         return ".pch";
     }
diff --git a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/WindowsResourceCompiler.java b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/WindowsResourceCompiler.java
index f9b8bfc..ed41b07 100644
--- a/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/WindowsResourceCompiler.java
+++ b/subprojects/platform-native/src/main/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/WindowsResourceCompiler.java
@@ -32,6 +32,9 @@ class WindowsResourceCompiler extends VisualCppNativeCompiler<WindowsResourceCom
 
     @Override
     protected Iterable<String> buildPerFileArgs(List<String> genericArgs, List<String> sourceArgs, List<String> outputArgs, List<String> pchArgs) {
+        if (pchArgs != null && !pchArgs.isEmpty()) {
+            throw new UnsupportedOperationException("Precompiled header arguments cannot be specified for a Windows Resource compiler.");
+        }
         // RC has position sensitive arguments, the output args need to appear before the source file
         return Iterables.concat(genericArgs, outputArgs, sourceArgs);
     }
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeComponentTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeComponentTest.groovy
index c83fc43..8d493e5 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeComponentTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeComponentTest.groovy
@@ -15,13 +15,15 @@
  */
 
 package org.gradle.nativeplatform.internal
+
 import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.ClassGeneratorBackedInstantiator
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
@@ -33,7 +35,7 @@ class DefaultNativeComponentTest extends Specification {
 
     def setup(){
         mainSourceSet = new DefaultFunctionalSourceSet("testFunctionalSourceSet", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet))
-        component = BaseComponentSpec.create(TestNativeComponentSpec, id, mainSourceSet, instantiator)
+        component = BaseComponentFixtures.create(TestNativeComponentSpec, new ModelRegistryHelper(), id, mainSourceSet, instantiator)
     }
 
     def "flavors can be chosen and will replace default flavor"() {
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableBinarySpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableBinarySpecTest.groovy
index d00f112..0a7b63a 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableBinarySpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableBinarySpecTest.groovy
@@ -20,15 +20,14 @@ import org.gradle.api.internal.project.taskfactory.ITaskFactory
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.nativeplatform.BuildType
-import org.gradle.nativeplatform.internal.configure.DefaultNativeBinariesFactory
+import org.gradle.nativeplatform.internal.configure.TestNativeBinariesFactory
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
 import org.gradle.nativeplatform.platform.NativePlatform
 import org.gradle.nativeplatform.tasks.InstallExecutable
 import org.gradle.nativeplatform.tasks.LinkExecutable
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultBinaryNamingScheme
 import org.gradle.platform.base.internal.DefaultBinaryTasksCollection
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
@@ -38,14 +37,16 @@ import spock.lang.Specification
 class DefaultNativeExecutableBinarySpecTest extends Specification {
     def instantiator = DirectInstantiator.INSTANCE
     def namingScheme = new DefaultBinaryNamingScheme("bigOne", "executable", [])
-    def tasks = new DefaultNativeExecutableBinarySpec.DefaultTasksCollection(new DefaultBinaryTasksCollection(null, Mock(ITaskFactory)))
+    def taskFactory = Mock(ITaskFactory)
+    def tasks = new DefaultNativeExecutableBinarySpec.DefaultTasksCollection(new DefaultBinaryTasksCollection(null, taskFactory))
 
     def "has useful string representation"() {
         given:
-        def executable = BaseComponentSpec.create(DefaultNativeExecutableSpec, new DefaultComponentSpecIdentifier("path", "name"), new DefaultFunctionalSourceSet("name", instantiator, Stub(ProjectSourceSet)), instantiator)
+        def executable = BaseComponentFixtures.create(DefaultNativeExecutableSpec, new ModelRegistryHelper(), new DefaultComponentSpecIdentifier("path", "name"), new DefaultFunctionalSourceSet("name", instantiator, Stub(ProjectSourceSet)), instantiator)
 
         when:
-        def binary = DefaultNativeBinariesFactory.create(DefaultNativeExecutableBinarySpec, instantiator, executable, namingScheme, Mock(NativeDependencyResolver), Stub(NativeToolChainInternal), Stub(PlatformToolProvider), Stub(NativePlatform), Stub(BuildType), new DefaultFlavor("flavorOne"), Mock(ITaskFactory))
+        def binary = TestNativeBinariesFactory.create(DefaultNativeExecutableBinarySpec, namingScheme.getLifecycleTaskName(), instantiator, taskFactory, executable, namingScheme,
+            Mock(NativeDependencyResolver), Stub(NativePlatform), Stub(BuildType), new DefaultFlavor("flavorOne"))
 
         then:
         binary.toString() == "executable 'bigOne:executable'"
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableSpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableSpecTest.groovy
index 162e1f2..ef76ded 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableSpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeExecutableSpecTest.groovy
@@ -15,17 +15,19 @@
  */
 
 package org.gradle.nativeplatform.internal
+
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
 class DefaultNativeExecutableSpecTest extends Specification {
     def instantiator = DirectInstantiator.INSTANCE
     def mainSourceSet = new DefaultFunctionalSourceSet("testFS", instantiator, Stub(ProjectSourceSet))
-    def executable = BaseComponentSpec.create(DefaultNativeExecutableSpec, new DefaultComponentSpecIdentifier("project-path", "someExe"), mainSourceSet, instantiator)
+    def executable = BaseComponentFixtures.create(DefaultNativeExecutableSpec, new ModelRegistryHelper(), new DefaultComponentSpecIdentifier("project-path", "someExe"), mainSourceSet, instantiator)
 
     def "has useful string representation"() {
         expect:
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeLibrarySpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeLibrarySpecTest.groovy
index d696e61..22eed9d 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeLibrarySpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultNativeLibrarySpecTest.groovy
@@ -20,13 +20,14 @@ import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
 class DefaultNativeLibrarySpecTest extends Specification {
     FunctionalSourceSet mainSourceSet = new DefaultFunctionalSourceSet("testFS", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet))
-    final library = BaseComponentSpec.create(DefaultNativeLibrarySpec, new DefaultComponentSpecIdentifier("project-path", "someLib"), mainSourceSet, DirectInstantiator.INSTANCE)
+    final library = BaseComponentFixtures.create(DefaultNativeLibrarySpec, new ModelRegistryHelper(), new DefaultComponentSpecIdentifier("project-path", "someLib"), mainSourceSet, DirectInstantiator.INSTANCE)
 
     def "has useful string representation"() {
         expect:
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultSharedLibraryBinarySpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultSharedLibraryBinarySpecTest.groovy
index 26c90ba..789d712 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultSharedLibraryBinarySpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultSharedLibraryBinarySpecTest.groovy
@@ -24,13 +24,14 @@ import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
 import org.gradle.language.nativeplatform.HeaderExportingSourceSet
 import org.gradle.language.nativeplatform.NativeResourceSet
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.nativeplatform.BuildType
+import org.gradle.nativeplatform.internal.configure.TestNativeBinariesFactory
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
 import org.gradle.nativeplatform.platform.NativePlatform
 import org.gradle.nativeplatform.tasks.LinkSharedLibrary
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultBinaryNamingScheme
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
@@ -38,8 +39,6 @@ import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
-import static org.gradle.nativeplatform.internal.configure.DefaultNativeBinariesFactory.create
-
 class DefaultSharedLibraryBinarySpecTest extends Specification {
     @Rule
     TestNameTestDirectoryProvider tmpDir
@@ -151,7 +150,9 @@ class DefaultSharedLibraryBinarySpecTest extends Specification {
     }
 
     private DefaultSharedLibraryBinarySpec getSharedLibrary() {
-        final library = BaseComponentSpec.create(DefaultNativeLibrarySpec, new DefaultComponentSpecIdentifier("path", "libName"), new DefaultFunctionalSourceSet("name", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet)), instantiator);
-        return create(DefaultSharedLibraryBinarySpec, instantiator, library, namingScheme, resolver, toolChain, Stub(PlatformToolProvider), platform, buildType, new DefaultFlavor("flavorOne"), Mock(ITaskFactory))
+        final library = BaseComponentFixtures.create(DefaultNativeLibrarySpec, new ModelRegistryHelper(), new DefaultComponentSpecIdentifier("path", "libName"),
+            new DefaultFunctionalSourceSet("name", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet)), instantiator);
+        TestNativeBinariesFactory.create(DefaultSharedLibraryBinarySpec, "test", instantiator, Mock(ITaskFactory), library, namingScheme, resolver,
+            platform, buildType, new DefaultFlavor("flavorOne"))
     }
 }
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultStaticLibraryBinarySpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultStaticLibraryBinarySpecTest.groovy
index 143e8dc..a8e102a 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultStaticLibraryBinarySpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/DefaultStaticLibraryBinarySpecTest.groovy
@@ -24,13 +24,14 @@ import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
 import org.gradle.language.nativeplatform.HeaderExportingSourceSet
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.nativeplatform.BuildType
+import org.gradle.nativeplatform.internal.configure.TestNativeBinariesFactory
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
 import org.gradle.nativeplatform.platform.NativePlatform
 import org.gradle.nativeplatform.tasks.CreateStaticLibrary
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultBinaryNamingScheme
 import org.gradle.platform.base.internal.DefaultBinaryTasksCollection
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
@@ -40,13 +41,11 @@ import org.gradle.util.TestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
-import static org.gradle.nativeplatform.internal.configure.DefaultNativeBinariesFactory.create
-
 class DefaultStaticLibraryBinarySpecTest extends Specification {
     @Rule
     TestNameTestDirectoryProvider tmpDir
     def instantiator = DirectInstantiator.INSTANCE
-    final library = BaseComponentSpec.create(DefaultNativeLibrarySpec, new DefaultComponentSpecIdentifier("path", "libName"), new DefaultFunctionalSourceSet("name", instantiator, Stub(ProjectSourceSet)), instantiator)
+    final library = BaseComponentFixtures.create(DefaultNativeLibrarySpec, new ModelRegistryHelper(), new DefaultComponentSpecIdentifier("path", "libName"), new DefaultFunctionalSourceSet("name", instantiator, Stub(ProjectSourceSet)), instantiator)
     def namingScheme = new DefaultBinaryNamingScheme("main", "staticLibrary", [])
     def toolChain = Stub(NativeToolChainInternal)
     def platform = Stub(NativePlatform)
@@ -61,7 +60,8 @@ class DefaultStaticLibraryBinarySpecTest extends Specification {
     }
 
     def getStaticLibrary() {
-        create(DefaultStaticLibraryBinarySpec, instantiator, library, namingScheme, resolver, toolChain, Stub(PlatformToolProvider), platform, buildType, new DefaultFlavor("flavorOne"), Mock(ITaskFactory))
+        TestNativeBinariesFactory.create(DefaultStaticLibraryBinarySpec, "test", instantiator, Mock(ITaskFactory), library, namingScheme, resolver, platform,
+            buildType, new DefaultFlavor("flavorOne"))
     }
 
     def "can set output file"() {
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecTest.groovy
index 7f7d376..d505520 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/NativeBinarySpecTest.groovy
@@ -22,28 +22,27 @@ import org.gradle.language.base.LanguageSourceSet
 import org.gradle.language.base.ProjectSourceSet
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet
 import org.gradle.language.nativeplatform.DependentSourceSet
+import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.nativeplatform.*
+import org.gradle.nativeplatform.internal.configure.TestNativeBinariesFactory
 import org.gradle.nativeplatform.internal.resolve.NativeBinaryResolveResult
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
 import org.gradle.nativeplatform.platform.NativePlatform
 import org.gradle.nativeplatform.platform.internal.Architectures
 import org.gradle.nativeplatform.tasks.ObjectFilesToBinary
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
+import org.gradle.platform.base.component.BaseComponentFixtures
 import org.gradle.platform.base.internal.DefaultBinaryNamingScheme
 import org.gradle.platform.base.internal.DefaultBinaryTasksCollection
 import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
 import spock.lang.Specification
 
-import static org.gradle.nativeplatform.internal.configure.DefaultNativeBinariesFactory.create
-
 class NativeBinarySpecTest extends Specification {
     def instantiator = DirectInstantiator.INSTANCE
     def flavor1 = new DefaultFlavor("flavor1")
     def id = new DefaultComponentSpecIdentifier("project", "name")
     def sourceSet = new DefaultFunctionalSourceSet("testFunctionalSourceSet", instantiator, Stub(ProjectSourceSet))
-    def component = BaseComponentSpec.create(TestNativeComponentSpec, id, sourceSet, instantiator)
+    def component = BaseComponentFixtures.create(TestNativeComponentSpec, new ModelRegistryHelper(), id, sourceSet, instantiator)
 
     def toolChain1 = Stub(NativeToolChainInternal) {
         getName() >> "ToolChain1"
@@ -187,7 +186,8 @@ class NativeBinarySpecTest extends Specification {
     }
 
     def testBinary(NativeComponentSpec owner, Flavor flavor = new DefaultFlavor(DefaultFlavor.DEFAULT)) {
-        return create(TestNativeBinarySpec, instantiator, owner, new DefaultBinaryNamingScheme("baseName", "", []), resolver, toolChain1, Stub(PlatformToolProvider), platform1, buildType1, flavor, Mock(ITaskFactory))
+        TestNativeBinariesFactory.create(TestNativeBinarySpec, "test", instantiator, Mock(ITaskFactory), owner, new DefaultBinaryNamingScheme("baseName", "", []), resolver
+            , platform1, buildType1, flavor)
     }
 
     static class TestNativeComponentSpec extends AbstractNativeComponentSpec {
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactoryTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactoryTest.groovy
deleted file mode 100644
index 7ca9981..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/DefaultNativeBinariesFactoryTest.groovy
+++ /dev/null
@@ -1,103 +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.nativeplatform.internal.configure
-
-import org.gradle.api.Action
-import org.gradle.api.internal.project.taskfactory.ITaskFactory
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.language.base.ProjectSourceSet
-import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.nativeplatform.*
-import org.gradle.nativeplatform.internal.DefaultNativeExecutableSpec
-import org.gradle.nativeplatform.internal.DefaultNativeLibrarySpec
-import org.gradle.nativeplatform.internal.NativeBinarySpecInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
-import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
-import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
-import org.gradle.nativeplatform.platform.NativePlatform
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.platform.base.internal.DefaultBinaryNamingSchemeBuilder
-import spock.lang.Specification
-
-class DefaultNativeBinariesFactoryTest extends Specification {
-    def resolver = Mock(NativeDependencyResolver)
-    Action<NativeBinarySpec> configAction = Mock(Action)
-
-    def toolChain = Mock(NativeToolChainInternal)
-    def toolProvider = Mock(PlatformToolProvider)
-    def platform = Mock(NativePlatform)
-    def buildType = Mock(BuildType)
-    def flavor = Mock(Flavor)
-
-    def id = new DefaultComponentSpecIdentifier("project", "name")
-
-    def namingSchemeBuilder = new DefaultBinaryNamingSchemeBuilder().withComponentName("test")
-    def instantiator = DirectInstantiator.INSTANCE;
-    def factory = new DefaultNativeBinariesFactory(instantiator, configAction, resolver, Mock(ITaskFactory))
-    def mainSourceSet = new DefaultFunctionalSourceSet("testFunctionalSourceSet", instantiator, Stub(ProjectSourceSet));
-
-    def "creates binaries for executable"() {
-        given:
-        def executable = BaseComponentSpec.create(DefaultNativeExecutableSpec, id, mainSourceSet, instantiator)
-
-        when:
-        1 * configAction.execute(_)
-
-        and:
-        factory.createNativeBinaries(executable, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-
-        then:
-        executable.binaries.size() == 1
-        def binary = (executable.binaries as List)[0] as NativeBinarySpecInternal
-        binary.name == "testExecutable"
-        binary.toolChain == toolChain
-        binary.platformToolProvider == toolProvider
-        binary.targetPlatform == platform
-        binary.buildType == buildType
-        binary.flavor == flavor
-    }
-
-    def "creates binaries for library"() {
-        given:
-        def library = BaseComponentSpec.create(DefaultNativeLibrarySpec.class, id, mainSourceSet, DirectInstantiator.INSTANCE)
-
-        when:
-        2 * configAction.execute(_)
-
-        and:
-        factory.createNativeBinaries(library, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-
-        then:
-        library.binaries.size() == 2
-        def sharedLibrary = (library.binaries.withType(SharedLibraryBinarySpec) as List)[0] as NativeBinarySpecInternal
-        sharedLibrary.name == "testSharedLibrary"
-        sharedLibrary.toolChain == toolChain
-        sharedLibrary.platformToolProvider == toolProvider
-        sharedLibrary.targetPlatform == platform
-        sharedLibrary.buildType == buildType
-        sharedLibrary.flavor == flavor
-
-        def staticLibrary = (library.binaries.withType(SharedLibraryBinarySpec) as List)[0] as NativeBinarySpecInternal
-        staticLibrary.name == "testSharedLibrary"
-        staticLibrary.toolChain == toolChain
-        staticLibrary.platformToolProvider == toolProvider
-        staticLibrary.targetPlatform == platform
-        staticLibrary.buildType == buildType
-        staticLibrary.flavor == flavor
-    }
-}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRulesTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRulesTest.groovy
new file mode 100644
index 0000000..ecb9d6d
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinaryRulesTest.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.nativeplatform.internal.configure
+
+import org.gradle.api.Project
+import org.gradle.nativeplatform.NativeComponentSpec
+import org.gradle.nativeplatform.NativeExecutableSpec
+import org.gradle.nativeplatform.NativeLibrarySpec
+import org.gradle.nativeplatform.internal.NativeBinarySpecInternal
+import org.gradle.nativeplatform.internal.NativeExecutableBinarySpecInternal
+import org.gradle.nativeplatform.internal.SharedLibraryBinarySpecInternal
+import org.gradle.nativeplatform.internal.StaticLibraryBinarySpecInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
+import org.gradle.nativeplatform.toolchain.internal.NativeToolChainRegistryInternal
+import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.platform.base.internal.BinaryNamingScheme
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class NativeBinaryRulesTest extends Specification {
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+
+    def project = Mock(Project)
+
+    def namingScheme = Mock(BinaryNamingScheme)
+    def toolProvider = Mock(PlatformToolProvider)
+    def platform = Mock(NativePlatformInternal)
+    def toolChains = Mock(NativeToolChainRegistryInternal) {
+        getForPlatform(platform) >> Mock(NativeToolChainInternal) {
+            select(platform) >> toolProvider
+        }
+    }
+
+    def setup() {
+        project.buildDir >> tmpDir.testDirectory
+    }
+
+    def "test executable"() {
+        def binary = initBinary(NativeExecutableBinarySpecInternal, NativeExecutableSpec)
+
+        when:
+        toolProvider.getExecutableName("base_name") >> "exe_name"
+
+        and:
+        NativeBinaryRules.assignTools(binary, toolChains, tmpDir.testDirectory)
+
+        then:
+        1 * binary.setExecutableFile(tmpDir.testDirectory.file("binaries", "output_dir", "exe_name"))
+    }
+
+    def "test shared library"() {
+        def binary = initBinary(SharedLibraryBinarySpecInternal, NativeLibrarySpec)
+
+        when:
+        toolProvider.getSharedLibraryName("base_name") >> "shared_library_name"
+        toolProvider.getSharedLibraryLinkFileName("base_name") >> "shared_library_link_name"
+
+        and:
+        NativeBinaryRules.assignTools(binary, toolChains, tmpDir.testDirectory)
+
+        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(StaticLibraryBinarySpecInternal, NativeLibrarySpec)
+
+        when:
+        toolProvider.getStaticLibraryName("base_name") >> "static_library_name"
+
+        and:
+        NativeBinaryRules.assignTools(binary, toolChains, tmpDir.testDirectory)
+
+        then:
+        1 * binary.setStaticLibraryFile(tmpDir.testDirectory.file("binaries", "output_dir", "static_library_name"))
+    }
+
+    private <T extends NativeBinarySpecInternal> T initBinary(Class<T> type, Class<? extends NativeComponentSpec> componentType) {
+        def binary = Mock(type)
+        def component = Stub(componentType)
+        binary.component >> component
+        binary.platformToolProvider >> toolProvider
+        binary.namingScheme >> namingScheme
+        binary.targetPlatform >> platform
+
+
+        namingScheme.outputDirectoryBase >> "output_dir"
+        component.baseName >> "base_name"
+        return binary
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializerTest.groovy
deleted file mode 100644
index fb2ba38..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeBinarySpecInitializerTest.groovy
+++ /dev/null
@@ -1,99 +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.nativeplatform.internal.configure
-
-import org.gradle.api.Project
-import org.gradle.nativeplatform.NativeComponentSpec
-import org.gradle.nativeplatform.NativeExecutableSpec
-import org.gradle.nativeplatform.NativeLibrarySpec
-import org.gradle.nativeplatform.internal.NativeBinarySpecInternal
-import org.gradle.nativeplatform.internal.NativeExecutableBinarySpecInternal
-import org.gradle.nativeplatform.internal.SharedLibraryBinarySpecInternal
-import org.gradle.nativeplatform.internal.StaticLibraryBinarySpecInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.internal.BinaryNamingScheme
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-class NativeBinarySpecInitializerTest extends Specification {
-    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-
-    def project = Mock(Project)
-    def configAction
-
-    def namingScheme = Mock(BinaryNamingScheme)
-    def toolProvider = Mock(PlatformToolProvider)
-
-    def setup() {
-        project.buildDir >> tmpDir.testDirectory
-        configAction = new NativeBinarySpecInitializer(project.buildDir)
-    }
-
-    def "test executable"() {
-        def binary = initBinary(NativeExecutableBinarySpecInternal, NativeExecutableSpec)
-
-        when:
-        toolProvider.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(SharedLibraryBinarySpecInternal, NativeLibrarySpec)
-
-        when:
-        toolProvider.getSharedLibraryName("base_name") >> "shared_library_name"
-        toolProvider.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(StaticLibraryBinarySpecInternal, NativeLibrarySpec)
-
-        when:
-        toolProvider.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 NativeBinarySpecInternal> T initBinary(Class<T> type, Class<? extends NativeComponentSpec> componentType) {
-        def binary = Mock(type)
-        def component = Stub(componentType)
-        binary.component >> component
-        binary.platformToolProvider >> toolProvider
-        binary.namingScheme >> namingScheme
-
-        namingScheme.outputDirectoryBase >> "output_dir"
-        component.baseName >> "base_name"
-        return binary
-    }
-}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRulesTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRulesTest.groovy
new file mode 100644
index 0000000..dd35b0e
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentRulesTest.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.nativeplatform.internal.configure
+
+import org.gradle.api.Named
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.language.base.ProjectSourceSet
+import org.gradle.language.base.internal.DefaultFunctionalSourceSet
+import org.gradle.model.internal.fixture.ModelRegistryHelper
+import org.gradle.nativeplatform.BuildType
+import org.gradle.nativeplatform.Flavor
+import org.gradle.nativeplatform.internal.DefaultNativeLibrarySpec
+import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver
+import org.gradle.nativeplatform.platform.NativePlatform
+import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
+import org.gradle.nativeplatform.platform.internal.NativePlatforms
+import org.gradle.platform.base.component.BaseComponentFixtures
+import org.gradle.platform.base.internal.DefaultBinaryNamingSchemeBuilder
+import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
+import org.gradle.platform.base.internal.DefaultPlatformRequirement
+import org.gradle.platform.base.internal.PlatformResolvers
+import spock.lang.Specification
+
+class NativeComponentRulesTest extends Specification {
+    def instantiator = DirectInstantiator.INSTANCE
+    def namingSchemeBuilder = Spy(DefaultBinaryNamingSchemeBuilder)
+    def platforms = Mock(PlatformResolvers)
+    def nativePlatforms = Stub(NativePlatforms)
+    def nativeDependencyResolver = Mock(NativeDependencyResolver)
+    def platform = createStub(NativePlatformInternal, "platform1")
+
+    def buildType = createStub(BuildType, "buildType1")
+    def flavor = createStub(Flavor, "flavor1")
+
+    def id = new DefaultComponentSpecIdentifier("project", "name")
+    def mainSourceSet = new DefaultFunctionalSourceSet("testFSS", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet))
+    def component = BaseComponentFixtures.create(DefaultNativeLibrarySpec.class, new ModelRegistryHelper(), id, mainSourceSet, instantiator)
+
+    def "does not use variant dimension names for single valued dimensions"() {
+        component.targetPlatform("platform1")
+
+        when:
+        NativeComponentRules.createBinariesImpl(component, platforms, [buildType].toSet(), [flavor].toSet(), nativePlatforms, nativeDependencyResolver, namingSchemeBuilder)
+
+        then:
+        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
+        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
+        0 * namingSchemeBuilder.withVariantDimension(_)
+    }
+
+    def "does not use variant dimension names when component targets a single point on dimension"() {
+        when:
+        component.targetPlatform("platform1")
+        component.targetBuildTypes("buildType1")
+        component.targetFlavors("flavor1")
+        NativeComponentRules.createBinariesImpl(component, platforms, [buildType].toSet(), [flavor].toSet(), nativePlatforms, nativeDependencyResolver, namingSchemeBuilder)
+
+        then:
+        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
+        component.binaries.keySet() == [
+            "nameSharedLibrary",
+            "nameStaticLibrary",
+        ].toSet()
+    }
+
+    def "includes platform in name for when multiple platforms"() {
+        def platform2 = createStub(NativePlatformInternal, "platform2")
+        component.targetPlatform("platform1")
+        component.targetPlatform("platform2")
+
+        when:
+        NativeComponentRules.createBinariesImpl(component, platforms, [buildType].toSet(), [flavor].toSet(), nativePlatforms, nativeDependencyResolver, namingSchemeBuilder)
+
+        then:
+        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
+        1 * platforms.resolve(NativePlatform, requirement("platform2")) >> platform2
+
+        then:
+        component.binaries.keySet() == [
+            "platform1NameStaticLibrary",
+            "platform1NameSharedLibrary",
+            "platform2NameStaticLibrary",
+            "platform2NameSharedLibrary",
+        ].toSet()
+    }
+
+    def "includes buildType in name for when multiple buildTypes"() {
+        final BuildType buildType2 = createStub(BuildType, "buildType2")
+        component.targetPlatform("platform1")
+
+        when:
+        NativeComponentRules.createBinariesImpl(component, platforms, [buildType, buildType2].toSet(), [flavor].toSet(), nativePlatforms, nativeDependencyResolver, namingSchemeBuilder)
+
+        then:
+        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
+        component.binaries.keySet() == [
+            "buildType1NameSharedLibrary",
+            "buildType1NameStaticLibrary",
+            "buildType2NameSharedLibrary",
+            "buildType2NameStaticLibrary",
+        ].toSet()
+    }
+
+    def "includes flavor in name for when multiple flavors"() {
+        component.targetPlatform("platform1")
+        final Flavor flavor2 = createStub(Flavor, "flavor2")
+
+        when:
+        NativeComponentRules.createBinariesImpl(component, platforms, [buildType].toSet(), [flavor, flavor2].toSet(), nativePlatforms, nativeDependencyResolver, namingSchemeBuilder)
+
+        then:
+        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
+        component.binaries.keySet() == [
+            "flavor1NameSharedLibrary",
+            "flavor1NameStaticLibrary",
+            "flavor2NameSharedLibrary",
+            "flavor2NameStaticLibrary",
+        ].toSet()
+    }
+
+    def requirement(String name) {
+        DefaultPlatformRequirement.create(name)
+    }
+
+    private <T extends Named> T createStub(Class<T> type, def name) {
+        def stub = Stub(type) {
+            getName() >> name
+        }
+        return stub
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializerTest.groovy
deleted file mode 100644
index 61939a9..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/configure/NativeComponentSpecInitializerTest.groovy
+++ /dev/null
@@ -1,180 +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.nativeplatform.internal.configure
-import org.gradle.api.Named
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.language.base.ProjectSourceSet
-import org.gradle.language.base.internal.DefaultFunctionalSourceSet
-import org.gradle.nativeplatform.BuildType
-import org.gradle.nativeplatform.Flavor
-import org.gradle.nativeplatform.internal.DefaultNativeExecutableSpec
-import org.gradle.nativeplatform.platform.NativePlatform
-import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
-import org.gradle.nativeplatform.toolchain.internal.NativeToolChainRegistryInternal
-import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
-import org.gradle.platform.base.component.BaseComponentSpec
-import org.gradle.platform.base.internal.BinaryNamingSchemeBuilder
-import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier
-import org.gradle.platform.base.internal.DefaultPlatformRequirement
-import org.gradle.platform.base.internal.PlatformResolvers
-import spock.lang.Specification
-
-class NativeComponentSpecInitializerTest extends Specification {
-    def instantiator = DirectInstantiator.INSTANCE
-    def toolChains = Mock(NativeToolChainRegistryInternal)
-    def toolChain = Mock(NativeToolChainInternal)
-    def toolProvider = Mock(PlatformToolProvider)
-    def nativeBinariesFactory = Mock(NativeBinariesFactory)
-    def namingSchemeBuilder = Mock(BinaryNamingSchemeBuilder)
-    def platforms = Mock(PlatformResolvers)
-    def platform = createStub(NativePlatformInternal, "platform1")
-
-    def buildType = createStub(BuildType, "buildType1")
-    def flavor = createStub(Flavor, "flavor1")
-
-    def id = new DefaultComponentSpecIdentifier("project", "name")
-    def mainSourceSet = new DefaultFunctionalSourceSet("testFSS", DirectInstantiator.INSTANCE, Stub(ProjectSourceSet));
-    def component = BaseComponentSpec.create(DefaultNativeExecutableSpec, id, mainSourceSet, instantiator)
-
-    def "does not use variant dimension names for single valued dimensions"() {
-        component.targetPlatform("platform1")
-
-        when:
-        def factory = new NativeComponentSpecInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains, platforms, [buildType], [flavor])
-        factory.execute(component)
-
-        then:
-        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
-        1 * toolChains.getForPlatform(platform) >> toolChain
-        1 * toolChain.select(platform) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-        0 * namingSchemeBuilder._
-    }
-
-    def "does not use variant dimension names when component targets a single point on dimension"() {
-        when:
-        def factory = new NativeComponentSpecInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
-                platforms, [buildType, Mock(BuildType)], [flavor, Mock(Flavor)])
-        component.targetPlatform("platform1")
-        component.targetBuildTypes("buildType1")
-        component.targetFlavors("flavor1")
-        factory.execute(component)
-
-        then:
-        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
-        1 * toolChains.getForPlatform(platform) >> toolChain
-        1 * toolChain.select(platform) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-        0 * namingSchemeBuilder._
-    }
-
-    def "includes platform in name for when multiple platforms"() {
-        def platform2 = createStub(NativePlatformInternal, "platform2")
-        component.targetPlatform("platform1")
-        component.targetPlatform("platform2")
-
-        when:
-        def factory = new NativeComponentSpecInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
-                platforms, [buildType], [flavor])
-        factory.execute(component)
-
-
-        then:
-        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
-        1 * platforms.resolve(NativePlatform, requirement("platform2")) >> platform2
-
-        1 * toolChains.getForPlatform(platform) >> toolChain
-        1 * toolChain.select(platform) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-        1 * namingSchemeBuilder.withVariantDimension("platform1") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-        0 * _
-
-        then:
-        1 * toolChains.getForPlatform(platform2) >> toolChain
-        1 * toolChain.select(platform2) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-        1 * namingSchemeBuilder.withVariantDimension("platform2") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform2, buildType, flavor)
-        0 * _
-    }
-
-    def "includes buildType in name for when multiple buildTypes"() {
-        final BuildType buildType2 = createStub(BuildType, "buildType2")
-        component.targetPlatform("platform1")
-
-        when:
-        def factory = new NativeComponentSpecInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
-                platforms, [buildType, buildType2], [flavor])
-        factory.execute(component)
-
-        then:
-        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
-        1 * toolChains.getForPlatform(platform) >> toolChain
-        1 * toolChain.select(platform) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-
-        then:
-        1 * namingSchemeBuilder.withVariantDimension("buildType1") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-        0 * _
-
-        then:
-        1 * namingSchemeBuilder.withVariantDimension("buildType2") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType2, flavor)
-        0 * _
-    }
-
-    def "includes flavor in name for when multiple flavors"() {
-        component.targetPlatform("platform1")
-        final Flavor flavor2 = createStub(Flavor, "flavor2")
-        when:
-        def factory = new NativeComponentSpecInitializer(nativeBinariesFactory, namingSchemeBuilder, toolChains,
-                platforms, [buildType], [flavor, flavor2])
-        factory.execute(component)
-
-        then:
-        1 * platforms.resolve(NativePlatform, requirement("platform1")) >> platform
-        1 * toolChains.getForPlatform(platform) >> toolChain
-        1 * toolChain.select(platform) >> toolProvider
-        1 * namingSchemeBuilder.withComponentName("name") >> namingSchemeBuilder
-
-        then:
-        1 * namingSchemeBuilder.withVariantDimension("flavor1") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor)
-        0 * _
-
-        then:
-        1 * namingSchemeBuilder.withVariantDimension("flavor2") >> namingSchemeBuilder
-        1 * nativeBinariesFactory.createNativeBinaries(component, namingSchemeBuilder, toolChain, toolProvider, platform, buildType, flavor2)
-        0 * _
-    }
-
-    def requirement(String name) {
-        DefaultPlatformRequirement.create(name)
-    }
-
-    private <T extends Named> T createStub(Class<T> type, def name) {
-        def stub = Stub(type) {
-            getName() >> name
-        }
-        return stub
-    }
-}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy
index a41024e..8069e88 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/internal/resolve/ProjectLibraryBinaryLocatorTest.groovy
@@ -15,12 +15,15 @@
  */
 package org.gradle.nativeplatform.internal.resolve
 
-import org.gradle.api.NamedDomainObjectSet
 import org.gradle.api.UnknownDomainObjectException
 import org.gradle.api.UnknownProjectException
 import org.gradle.api.internal.DefaultDomainObjectSet
-import org.gradle.api.internal.plugins.ExtensionContainerInternal
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.model.ModelMap
+import org.gradle.model.internal.core.ModelPath
+import org.gradle.model.internal.registry.ModelRegistry
+import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.NativeBinarySpec
 import org.gradle.nativeplatform.NativeLibraryBinary
 import org.gradle.nativeplatform.NativeLibraryRequirement
@@ -31,16 +34,20 @@ import spock.lang.Specification
 
 class ProjectLibraryBinaryLocatorTest extends Specification {
     def project = Mock(ProjectInternal)
+    def modelRegistry = Mock(ModelRegistry)
     def projectLocator = Mock(ProjectLocator)
     def requirement = Mock(NativeLibraryRequirement)
     def library = Mock(NativeLibrarySpec)
     def binary = Mock(MockNativeLibraryBinary)
-    def binaries = new DefaultDomainObjectSet(NativeBinarySpec, [binary])
+    def binaries = Mock(ModelMap)
+    def nativeBinaries = Mock(ModelMap)
     def convertedBinaries = new DefaultDomainObjectSet(NativeLibraryBinary, [binary])
     def locator = new ProjectLibraryBinaryLocator(projectLocator)
 
     def setup() {
         library.binaries >> binaries
+        binaries.withType(NativeBinarySpec) >> nativeBinaries
+        nativeBinaries.values() >> [binary]
     }
 
     def "locates binaries for library in same project"() {
@@ -100,7 +107,7 @@ class ProjectLibraryBinaryLocatorTest extends Specification {
         and:
         projectLocator.locateProject("other") >> project
         def libraries = findLibraryContainer(project)
-        libraries.getByName("unknown") >> { throw new UnknownDomainObjectException("unknown") }
+        libraries.get("unknown") >> { null }
 
         and:
         locator.getBinaries(requirement)
@@ -115,9 +122,8 @@ class ProjectLibraryBinaryLocatorTest extends Specification {
 
         and:
         projectLocator.locateProject("other") >> project
-        def extensions = Mock(ExtensionContainerInternal)
-        project.getExtensions() >> extensions
-        extensions.findByName("libraries") >> null
+        project.modelRegistry >> modelRegistry
+        modelRegistry.find(ModelPath.path("components"), ModelTypes.modelMap(NativeLibrarySpec)) >> null
         project.path >> "project-path"
 
         and:
@@ -130,17 +136,16 @@ class ProjectLibraryBinaryLocatorTest extends Specification {
 
     private void findLibraryInProject() {
         def libraries = findLibraryContainer(project)
-        libraries.getByName("libName") >> library
+        libraries.containsKey("libName") >> true
+        libraries.get("libName") >> library
     }
 
     private findLibraryContainer(ProjectInternal project) {
-        def extensions = Mock(ExtensionContainerInternal)
         def components = Mock(ComponentSpecContainer)
-        def libraryContainer = Mock(NamedDomainObjectSet)
-        project.getExtensions() >> extensions
-        extensions.findByType(ComponentSpecContainer) >> components
-        components.withType(NativeLibrarySpec) >> libraryContainer
-        return libraryContainer
+        project.modelRegistry >> modelRegistry
+        modelRegistry.find(ModelPath.path("components"), ModelType.of(ComponentSpecContainer)) >> components
+        components.withType(NativeLibrarySpec.class) >> components
+        return components
     }
 
     interface MockNativeLibraryBinary extends NativeBinarySpec, NativeLibraryBinary {}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy
index f29d8ba..b67f775 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy
@@ -22,12 +22,14 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
 import org.gradle.model.internal.core.ModelPath
 import org.gradle.model.internal.fixture.ModelRegistryHelper
 import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.*
 import org.gradle.nativeplatform.internal.DefaultFlavor
 import org.gradle.nativeplatform.platform.internal.NativePlatformInternal
 import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry
 import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider
+import org.gradle.platform.base.ComponentSpec
 import org.gradle.platform.base.PlatformContainer
 import org.gradle.util.TestUtil
 import spock.lang.Specification
@@ -40,17 +42,25 @@ class NativeComponentModelPluginTest extends Specification {
         project.pluginManager.apply(NativeComponentModelPlugin)
     }
 
+    public <T> T realizeModelElement(String path, Class<T> type) {
+        realizeModelElement(path, ModelType.of(type))
+    }
+
+    public <T> T realizeModelElement(String path, ModelType<T> type) {
+        project.modelRegistry.realize(ModelPath.path(path), type)
+    }
+
     def "adds model extensions"() {
         expect:
-        project.modelRegistry.realize(ModelPath.path("toolChains"), ModelType.of(NativeToolChainRegistry)) != null
-        project.modelRegistry.realize(ModelPath.path("platforms"), ModelType.of(PlatformContainer)) != null
-        project.modelRegistry.realize(ModelPath.path("buildTypes"), ModelType.of(BuildTypeContainer)) != null
-        project.modelRegistry.realize(ModelPath.path("flavors"), ModelType.of(FlavorContainer)) != null
+        realizeModelElement("toolChains", NativeToolChainRegistry) != null
+        realizeModelElement("platforms", PlatformContainer) != null
+        realizeModelElement("buildTypes", BuildTypeContainer) != null
+        realizeModelElement("flavors", FlavorContainer) != null
     }
 
     def "does not provide a default tool chain"() {
         expect:
-        project.modelRegistry.realize(ModelPath.path("toolChains"), ModelType.of(NativeToolChainRegistry)).isEmpty()
+        realizeModelElement("toolChains", NativeToolChainRegistry).isEmpty()
     }
 
     def "adds default flavor to every binary"() {
@@ -80,10 +90,10 @@ class NativeComponentModelPluginTest extends Specification {
         realize()
 
         then:
-        one(project.modelRegistry.realize(ModelPath.path("toolChains"), ModelType.of(NativeToolChainRegistry))).name == 'tc'
-        project.modelRegistry.realize(ModelPath.path("platforms"), ModelType.of(PlatformContainer)).size() == 1
-        one(project.modelRegistry.realize(ModelPath.path("buildTypes"), ModelType.of(BuildTypeContainer))).name == 'bt'
-        one(project.modelRegistry.realize(ModelPath.path("flavors"), ModelType.of(FlavorContainer))).name == 'flavor1'
+        one(realizeModelElement("toolChains", NativeToolChainRegistry)).name == 'tc'
+        realizeModelElement("platforms", PlatformContainer).size() == 1
+        one(realizeModelElement("buildTypes", BuildTypeContainer)).name == 'bt'
+        one(realizeModelElement("flavors", FlavorContainer)).name == 'flavor1'
     }
 
     def "creates binaries for executable"() {
@@ -105,7 +115,7 @@ class NativeComponentModelPluginTest extends Specification {
         realize()
 
         then:
-        NativeExecutableSpec executable = one(project.componentSpecs) as NativeExecutableSpec
+        NativeExecutableSpec executable = one(realizeModelElement("components", ModelTypes.modelMap(ComponentSpec)).values()) as NativeExecutableSpec
         NativeExecutableBinarySpec executableBinary = one(project.binaries) as NativeExecutableBinarySpec
         with(executableBinary) {
             name == 'testExecutable'
@@ -117,7 +127,7 @@ class NativeComponentModelPluginTest extends Specification {
         }
 
         and:
-        executable.binaries == [executableBinary] as Set
+        executable.binaries.values() == [executableBinary]
     }
 
     def "creates binaries for library"() {
@@ -139,7 +149,7 @@ class NativeComponentModelPluginTest extends Specification {
         realize()
 
         then:
-        NativeLibrarySpec library = one(project.componentSpecs) as NativeLibrarySpec
+        NativeLibrarySpec library = one(realizeModelElement("components", ModelTypes.modelMap(ComponentSpec)).values()) as NativeLibrarySpec
         SharedLibraryBinarySpec sharedLibraryBinary = project.binaries.testSharedLibrary as SharedLibraryBinarySpec
         with(sharedLibraryBinary) {
             name == 'testSharedLibrary'
@@ -164,8 +174,8 @@ class NativeComponentModelPluginTest extends Specification {
         }
 
         and:
-        library.binaries.contains(sharedLibraryBinary)
-        library.binaries.contains(staticLibraryBinary)
+        library.binaries.values().contains(sharedLibraryBinary)
+        library.binaries.values().contains(staticLibraryBinary)
     }
 
     def "creates lifecycle task for each binary"() {
@@ -202,9 +212,12 @@ class NativeComponentModelPluginTest extends Specification {
         project.bindAllModelRules()
     }
 
-    static <T> T one(Collection<T> collection) {
-        assert collection.size() == 1
-        return collection.iterator().next()
+    static <T> T one(Iterable<T> iterable) {
+        def iterator = iterable.iterator()
+        assert iterator.hasNext()
+        def item = iterator.next()
+        assert !iterator.hasNext()
+        return item
     }
 
     public <T> T named(Class<T> type, def name) {
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompilerTest.groovy
index d131a42..b28666a 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompilerTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/NativeCompilerTest.groovy
@@ -120,6 +120,7 @@ public abstract class NativeCompilerTest extends Specification {
             getOperationLogger() >> Mock(BuildOperationLogger) {
                 getLogLocation() >> "<log location>"
             }
+            getPreCompiledHeader() >> null
             getPrefixHeaderFile() >> null
             getPreCompiledHeaderObjectFile() >> null
         }
@@ -154,6 +155,7 @@ public abstract class NativeCompilerTest extends Specification {
             getObjectFileDir() >> objectFileDir
             getSourceFiles() >> sourceFiles
             getOperationLogger() >> Mock(BuildOperationLogger)
+            getPreCompiledHeader() >> null
             getPrefixHeaderFile() >> null
             getPreCompiledHeaderObjectFile() >> null
         }
@@ -183,6 +185,7 @@ public abstract class NativeCompilerTest extends Specification {
             getIncludeRoots() >> [ includeDir ]
             getTempDir() >> testDir
             getOperationLogger() >> Mock(BuildOperationLogger)
+            getPreCompiledHeader() >> null
             getPrefixHeaderFile() >> null
             getPreCompiledHeaderObjectFile() >> null
         }
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtilsTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtilsTest.groovy
new file mode 100644
index 0000000..c1e67b8
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PCHUtilsTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal
+
+import com.google.common.collect.Lists
+import org.gradle.api.Transformer
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.TextUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+class PCHUtilsTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
+
+    def "generates a prefix header file" () {
+        def headers = Lists.newArrayList()
+        headers.add "header.h"
+        headers.add "<stdio.h>"
+        headers.add "some/path/to/another.h"
+        def tempDir = tmpDirProvider.createDir("temp")
+        def prefixHeaderFile = new File(tempDir, "prefix-headers.h")
+
+        when:
+        PCHUtils.generatePCHFile(headers, prefixHeaderFile)
+
+        then:
+        prefixHeaderFile.text == TextUtil.toPlatformLineSeparators(
+"""#include "header.h"
+#include <stdio.h>
+#include "some/path/to/another.h"
+""")
+    }
+
+    def "can generate a source file for a pre-compiled header" () {
+        given:
+        def tempDir = tmpDirProvider.createDir("temp")
+        def pchSourceDir = tempDir.createDir("pchGenerated")
+        def headerDir = tmpDirProvider.createDir("headers")
+        def sourceFile = headerDir.createFile("test.h")
+        def spec = Mock(type) {
+            getTempDir() >> tempDir
+        }
+
+        when:
+        def generated = PCHUtils.generatePCHSourceFile(spec, sourceFile)
+
+        then:
+        generated.name == "test.${extension}"
+        generated.parentFile == pchSourceDir
+        generated.text == "#include \"test.h\""
+        pchSourceDir.assertContainsDescendants("test.h", "test.${extension}")
+
+        where:
+        type              | extension
+        CPCHCompileSpec   | "c"
+        CppPCHCompileSpec | "cpp"
+    }
+
+    def "generates a PCH object directory" () {
+        given:
+        def tempDir = tmpDirProvider.createDir("temp")
+        def objectDir = tmpDirProvider.createDir("pch")
+        def prefixFile = tempDir.createFile("header.h")
+        def objectFile = tempDir.createFile("header.o")
+        prefixFile << "#include <stdio.h>"
+        objectFile << "some content"
+
+        when:
+        def generated = PCHUtils.generatePCHObjectDirectory(objectDir, prefixFile, objectFile)
+
+        then:
+        generated.parentFile == objectDir
+        generated.name == "preCompiledHeaders"
+        generated.listFiles().collect { it.name }.sort() == [ 'header.h', 'header.o' ]
+        new File(generated, "header.h").bytes == prefixFile.bytes
+        new File(generated, "header.o").bytes == objectFile.bytes
+    }
+
+    def "transforms pre-compiled header spec to contain generated source files" () {
+        given:
+        def tempDir = tmpDirProvider.createDir("temp")
+        def pchSourceDir = tempDir.createDir("pchGenerated")
+        def headerDir = tmpDirProvider.createDir("headers")
+        def sourceFile = headerDir.createFile("test.h")
+        Transformer<CPCHCompileSpec, CPCHCompileSpec> transformer = PCHUtils.getHeaderToSourceFileTransformer(CPCHCompileSpec)
+        def spec = Mock(CPCHCompileSpec) {
+            getTempDir() >> tempDir
+            getSourceFiles() >> [ sourceFile ]
+        }
+
+        when:
+        transformer.transform(spec)
+
+        then:
+        spec.setSourceFiles(_) >> { args ->
+            def sourceFiles = args[0]
+            assert sourceFiles.size() == 1
+            assert sourceFiles[0].name == "test.c"
+            assert sourceFiles[0].parentFile == pchSourceDir
+        }
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtilTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtilTest.groovy
deleted file mode 100644
index f733d49..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/PrefixHeaderFileGeneratorUtilTest.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal
-
-import com.google.common.collect.Sets
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.TextUtil
-import org.junit.Rule
-import spock.lang.Specification
-
-class PrefixHeaderFileGeneratorUtilTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
-
-    def "generates a prefix header file" () {
-        def headers = Sets.newLinkedHashSet()
-        headers.add "header.h"
-        headers.add "<stdio.h>"
-        headers.add "some/path/to/another.h"
-        def tempDir = tmpDirProvider.createDir("temp")
-        def prefixHeaderFile = new File(tempDir, "prefix-headers.h")
-
-        when:
-        PrefixHeaderFileGeneratorUtil.generatePCHFile(headers, prefixHeaderFile)
-
-        then:
-        prefixHeaderFile.text == TextUtil.toPlatformLineSeparators(
-"""#include "header.h"
-#include <stdio.h>
-#include "some/path/to/another.h"
-""")
-    }
-}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CPCHCompilerTest.groovy
new file mode 100644
index 0000000..86ba242
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CPCHCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
+
+class CPCHCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new CPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return CPCHCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'c-header' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CppPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CppPCHCompilerTest.groovy
new file mode 100644
index 0000000..b4d58d5
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/CppPCHCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec
+
+class CppPCHCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new CppPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return CppPCHCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'c++-header' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompilerTest.groovy
index d3740b2..a94efa6 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompilerTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccCompatibleNativeCompilerTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.nativeplatform.toolchain.internal.gcc
 
-import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
 import org.gradle.nativeplatform.toolchain.internal.NativeCompilerTest
 
 abstract class GccCompatibleNativeCompilerTest extends NativeCompilerTest {
@@ -32,7 +31,7 @@ abstract class GccCompatibleNativeCompilerTest extends NativeCompilerTest {
         def outputFile = testDir.file("output.ext")
 
         when:
-        def args = compiler.getOutputArgs(Stub(NativeCompileSpec), outputFile)
+        def args = compiler.getOutputArgs(outputFile)
 
         then:
         args == [ '-o', outputFile.absoluteFile.toString() ]
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCCompilerTest.groovy
new file mode 100644
index 0000000..ee70496
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCCompileSpec
+
+class ObjectiveCCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new ObjectiveCCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return ObjectiveCCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'objective-c' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCPCHCompilerTest.groovy
new file mode 100644
index 0000000..b5e7d94
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCPCHCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCPCHCompileSpec
+
+class ObjectiveCPCHCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new ObjectiveCPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return ObjectiveCPCHCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'objective-c-header' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppCompilerTest.groovy
new file mode 100644
index 0000000..afb5819
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCppCompileSpec
+
+class ObjectiveCppCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new ObjectiveCppCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return ObjectiveCppCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'objective-c++' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppPCHCompilerTest.groovy
new file mode 100644
index 0000000..55077a9
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/ObjectiveCppPCHCompilerTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.gcc
+
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.ObjectiveCppPCHCompileSpec
+
+class ObjectiveCppPCHCompilerTest extends GccCompatibleNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new ObjectiveCppPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return ObjectiveCppPCHCompileSpec
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '-x', 'objective-c++-header' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompilerTest.groovy
new file mode 100644
index 0000000..4156314
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CPCHCompilerTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.msvcpp
+
+import org.gradle.internal.Transformers
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
+
+class CPCHCompilerTest extends VisualCppNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new CPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, Transformers.noOpTransformer(), objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<CPCHCompileSpec> getCompileSpecType() {
+        return CPCHCompileSpec
+    }
+
+    @Override
+    String getObjectFileFlag() {
+        return '/Fp'
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '/Yc' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompilerTest.groovy
new file mode 100644
index 0000000..c2d5a86
--- /dev/null
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/CppPCHCompilerTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.toolchain.internal.msvcpp
+
+import org.gradle.internal.Transformers
+import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext
+import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
+import org.gradle.nativeplatform.toolchain.internal.NativeCompiler
+import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec
+
+
+class CppPCHCompilerTest extends VisualCppNativeCompilerTest {
+    @Override
+    protected NativeCompiler getCompiler(CommandLineToolContext invocationContext, String objectFileExtension, boolean useCommandFile) {
+        return new CppPCHCompiler(buildOperationProcessor, commandLineTool, invocationContext, Transformers.noOpTransformer(), objectFileExtension, useCommandFile)
+    }
+
+    @Override
+    protected Class<? extends NativeCompileSpec> getCompileSpecType() {
+        return CppPCHCompileSpec
+    }
+
+    @Override
+    String getObjectFileFlag() {
+        return '/Fp'
+    }
+
+    @Override
+    protected List<String> getCompilerSpecificArguments(File includeDir) {
+        return [ '/Yc' ] + super.getCompilerSpecificArguments(includeDir)
+    }
+}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompilerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompilerTest.groovy
index 206f280..0cdd363 100644
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompilerTest.groovy
+++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppNativeCompilerTest.groovy
@@ -16,10 +16,13 @@
 
 package org.gradle.nativeplatform.toolchain.internal.msvcpp
 
-import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec
 import org.gradle.nativeplatform.toolchain.internal.NativeCompilerTest
 
 abstract class VisualCppNativeCompilerTest extends NativeCompilerTest {
+    String getObjectFileFlag() {
+        return '/Fo'
+    }
+
     @Override
     protected List<String> getCompilerSpecificArguments(File includeDir) {
         ['/nologo', '/c', '/Dfoo=bar', '/Dempty', '-firstArg', '-secondArg',
@@ -31,13 +34,12 @@ abstract class VisualCppNativeCompilerTest extends NativeCompilerTest {
         def compiler = getCompiler()
         def testDir = tmpDirProvider.testDirectory
         def outputFile = testDir.file("output.ext")
-        def spec = Stub(NativeCompileSpec)
 
         when:
-        def args = compiler.getOutputArgs(spec, outputFile)
+        def args = compiler.getOutputArgs(outputFile)
 
         then:
-        args == ['/Fo' + outputFile.absoluteFile.toString()]
+        args == [objectFileFlag + outputFile.absoluteFile.toString()]
     }
 
 }
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtilTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtilTest.groovy
deleted file mode 100644
index b8aaa0b..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileGeneratorUtilTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal.msvcpp
-
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CppPCHCompileSpec
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-class VisualCppPCHSourceFileGeneratorUtilTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
-
-    def "can generate a source file for a pre-compiled header" () {
-        given:
-        def tempDir = tmpDirProvider.createDir("temp")
-        def pchSourceDir = tempDir.createDir("pchGeneratedSource")
-        def headerDir = tmpDirProvider.createDir("headers")
-        def sourceFile = headerDir.createFile("test.h")
-        def spec = Mock(type) {
-            getTempDir() >> tempDir
-        }
-
-        when:
-        def generated = VisualCppPCHSourceFileGeneratorUtil.generatePCHSourceFile(spec, sourceFile)
-
-        then:
-        generated.name == "test.${extension}"
-        generated.parentFile == pchSourceDir
-        generated.text == "#include \"test.h\""
-        pchSourceDir.assertContainsDescendants("test.h", "test.${extension}")
-
-        where:
-        type           | extension
-        CPCHCompileSpec   | "c"
-        CppPCHCompileSpec | "cpp"
-    }
-}
diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformerTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformerTest.groovy
deleted file mode 100644
index fe8de86..0000000
--- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/msvcpp/VisualCppPCHSourceFileTransformerTest.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.toolchain.internal.msvcpp
-
-import org.gradle.nativeplatform.toolchain.internal.compilespec.CPCHCompileSpec
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
-import spock.lang.Specification
-
-class VisualCppPCHSourceFileTransformerTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDirProvider = new TestNameTestDirectoryProvider()
-    def tempDir = tmpDirProvider.createDir("temp")
-    def pchSourceDir = tempDir.createDir("pchGeneratedSource")
-    def headerDir = tmpDirProvider.createDir("headers")
-    def sourceFile = headerDir.createFile("test.h")
-    VisualCppPCHSourceFileTransformer<CPCHCompileSpec> transformer = new VisualCppPCHSourceFileTransformer<CPCHCompileSpec>()
-
-    def "transforms pre-compiled header spec to contain generated source files" () {
-        def spec = Mock(CPCHCompileSpec) {
-            getTempDir() >> tempDir
-            getSourceFiles() >> [ sourceFile ]
-        }
-
-        when:
-        transformer.transform(spec)
-
-        then:
-        spec.setSourceFiles(_) >> { args ->
-            def sourceFiles = args[0]
-            assert sourceFiles.size() == 1
-            assert sourceFiles[0].name == "test.c"
-            assert sourceFiles[0].parentFile == pchSourceDir
-        }
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativePlatformsTestFixture.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativePlatformsTestFixture.java
index 6714386..c487fb0 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativePlatformsTestFixture.java
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativePlatformsTestFixture.java
@@ -16,22 +16,15 @@
 
 package org.gradle.nativeplatform.fixtures;
 
-import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform;
 import org.gradle.nativeplatform.platform.internal.NativePlatforms;
 import org.gradle.testfixtures.internal.NativeServicesTestFixture;
 
-import java.util.Set;
-
 public class NativePlatformsTestFixture {
     static {
         NativeServicesTestFixture.initialize();
     }
 
-    public static Set<DefaultNativePlatform> defaultPlatformDefinitions() {
-        return NativePlatforms.defaultPlatformDefinitions();
-    }
-
     public static String getDefaultPlatformName() {
-        return NativePlatforms.getDefaultPlatformName();
+        return new NativePlatforms().getDefaultPlatformName();
     }
 }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CHelloWorldApp.groovy
index 4796cda..5173cdb 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CHelloWorldApp.groovy
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CHelloWorldApp.groovy
@@ -38,6 +38,8 @@ class CHelloWorldApp extends IncrementalHelloWorldApp {
     @Override
     SourceFile getLibraryHeader() {
         sourceFile("headers", "hello.h", """
+            #ifndef HELLO_H
+            #define HELLO_H
             #ifdef _WIN32
             #define DLL_FUNC __declspec(dllexport)
             #else
@@ -46,13 +48,31 @@ class CHelloWorldApp extends IncrementalHelloWorldApp {
 
             void DLL_FUNC sayHello();
             int DLL_FUNC sum(int a, int b);
+
+            #ifdef FRENCH
+            #pragma message("<==== compiling bonjour.h ====>")
+            #else
+            #pragma message("<==== compiling hello.h ====>")
+            #endif
+
+            #endif
         """);
     }
 
+    @Override
+    def SourceFile getCommonHeader() {
+        sourceFile("headers", "common.h", """
+            #ifndef COMMON_H
+            #define COMMON_H
+            #include "hello.h"
+            #include <stdio.h>
+            #endif
+        """)
+    }
+
     List<SourceFile> librarySources = [
         sourceFile("c", "hello.c", """
-            #include <stdio.h>
-            #include "hello.h"
+            #include "common.h"
 
             #ifdef FRENCH
             char* greeting() {
@@ -74,7 +94,7 @@ class CHelloWorldApp extends IncrementalHelloWorldApp {
             }
         """),
         sourceFile("c", "sum.c","""
-            #include "hello.h"
+            #include "common.h"
 
             int DLL_FUNC sum(int a, int b) {
                 return a + b;
@@ -98,8 +118,7 @@ class CHelloWorldApp extends IncrementalHelloWorldApp {
 
     List<SourceFile> alternateLibrarySources = [
             sourceFile("c", "hello.c", """
-                #include <stdio.h>
-                #include "hello.h"
+                #include "common.h"
 
                 void DLL_FUNC sayHello() {
                     printf("[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]\\n");
@@ -112,7 +131,7 @@ class CHelloWorldApp extends IncrementalHelloWorldApp {
                 }
             """),
             sourceFile("c", "sum.c","""
-                #include "hello.h"
+                #include "common.h"
 
                 int DLL_FUNC sum(int a, int b) {
                     return a + b;
@@ -170,4 +189,9 @@ Run Summary:    Type  Total    Ran Passed Failed Inactive
     public SourceFile getBrokenFile() {
         return sourceFile("c", "broken.c", """'broken""")
     }
+
+    @Override
+    String getSourceSetType() {
+        return "CSourceSet"
+    }
 }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CPCHHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CPCHHelloWorldApp.groovy
deleted file mode 100644
index 6ef9d72..0000000
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CPCHHelloWorldApp.groovy
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.fixtures.app
-
-import org.gradle.integtests.fixtures.SourceFile
-
-class CPCHHelloWorldApp extends PCHHelloWorldApp {
-
-    @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() {
-        getLibraryHeader("")
-    }
-
-    @Override
-    SourceFile getLibraryHeader(String path) {
-        sourceFile("headers/${path}", "hello.h", """
-            #ifndef HELLO_H
-            #define 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);
-
-            #ifdef FRENCH
-            #pragma message("<==== compiling bonjour.h ====>")
-            #else
-            #pragma message("<==== compiling hello.h ====>")
-            #endif
-            #endif
-        """);
-    }
-
-    @Override
-    public TestApp getAlternate() {
-        return new TestApp() {
-            @Override
-            SourceFile getMainSource() {
-                return getAlternateMainSource()
-            }
-
-            @Override
-            SourceFile getLibraryHeader() {
-                return sourceFile("headers", "hello.h", """
-                    #ifndef HELLO_H
-                    #define 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);
-
-                    #pragma message("<==== compiling althello.h ====>")
-                    #endif
-                """);
-            }
-
-            @Override
-            List<SourceFile> getLibrarySources() {
-                return getAlternateLibrarySources()
-            }
-        }
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources() {
-        return getLibrarySources("")
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources(String headerPath) {
-        return [
-                sourceFile("c", "hello.c", """
-                #include "${headerPath}hello.h"
-                #include <stdio.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 "${headerPath}hello.h"
-
-                int DLL_FUNC sum(int a, int b) {
-                    return a + b;
-                }
-            """)
-        ]
-    }
-
-    @Override
-    SourceFile getSystemHeader() {
-        return getSystemHeader("")
-    }
-
-    @Override
-    SourceFile getSystemHeader(String path) {
-        sourceFile("headers/${path}", "systemHeader.h", """
-            #ifndef SYSTEMHEADER_H
-            #define SYSTEMHEADER_H
-            #ifdef _WIN32
-            #define DLL_FUNC __declspec(dllexport)
-            #else
-            #define DLL_FUNC
-            #endif
-            void DLL_FUNC systemCall();
-            #pragma message("<==== compiling systemHeader.h ====>")
-            #endif
-        """)
-    }
-
-    @Override
-    String getIOHeader() {
-        return "stdio.h"
-    }
-
-    @Override
-    SourceFile getAlternateMainSource() {
-        return getMainSource()
-    }
-
-    @Override
-    String getAlternateOutput() {
-        return null
-    }
-
-    @Override
-    List<SourceFile> getAlternateLibrarySources() {
-        return getLibrarySources()
-    }
-
-    @Override
-    String getAlternateLibraryOutput() {
-        return null
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CommonHeaderHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CommonHeaderHelloWorldApp.groovy
new file mode 100644
index 0000000..d61910d
--- /dev/null
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CommonHeaderHelloWorldApp.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.fixtures.app
+
+import org.gradle.integtests.fixtures.SourceFile
+
+
+abstract class CommonHeaderHelloWorldApp extends HelloWorldApp {
+    public abstract SourceFile getCommonHeader();
+    public abstract String getSourceSetType();
+
+    @Override
+    public TestNativeComponent getLibrary() {
+        return new TestNativeComponent() {
+            @Override
+            public List<SourceFile> getSourceFiles() {
+                return getLibrarySources();
+            }
+
+            @Override
+            public List<SourceFile> getHeaderFiles() {
+                return Arrays.asList(getLibraryHeader(), getCommonHeader());
+            }
+        };
+    }
+}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppHelloWorldApp.groovy
index daff0ce..2f7a23d 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppHelloWorldApp.groovy
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppHelloWorldApp.groovy
@@ -53,6 +53,8 @@ class CppHelloWorldApp extends IncrementalHelloWorldApp {
     @Override
     SourceFile getLibraryHeader() {
         return sourceFile("headers", "hello.h", """
+            #ifndef HELLO_H
+            #define HELLO_H
             #ifdef _WIN32
             #define DLL_FUNC __declspec(dllexport)
             #else
@@ -65,13 +67,31 @@ class CppHelloWorldApp extends IncrementalHelloWorldApp {
             };
 
             int DLL_FUNC sum(int a, int b);
+
+            #ifdef FRENCH
+            #pragma message("<==== compiling bonjour.h ====>")
+            #else
+            #pragma message("<==== compiling hello.h ====>")
+            #endif
+
+            #endif
         """);
     }
 
+    @Override
+    def SourceFile getCommonHeader() {
+        sourceFile("headers", "common.h", """
+            #ifndef COMMON_H
+            #define COMMON_H
+            #include "hello.h"
+            #include <iostream>
+            #endif
+        """)
+    }
+
     List<SourceFile> librarySources = [
         sourceFile("cpp", "hello.cpp", """
-            #include <iostream>
-            #include "hello.h"
+            #include "common.h"
 
             #ifdef FRENCH
             const char* greeting() {
@@ -88,7 +108,7 @@ class CppHelloWorldApp extends IncrementalHelloWorldApp {
             }
         """),
         sourceFile("cpp", "sum.cpp", """
-            #include "hello.h"
+            #include "common.h"
 
             int DLL_FUNC sum(int a, int b) {
                 return a + b;
@@ -98,8 +118,7 @@ class CppHelloWorldApp extends IncrementalHelloWorldApp {
 
     List<SourceFile> alternateLibrarySources = [
         sourceFile("cpp", "hello.cpp", """
-            #include <iostream>
-            #include "hello.h"
+            #include "common.h"
 
             void DLL_FUNC Greeter::sayHello() {
                 std::cout << "[${HELLO_WORLD} - ${HELLO_WORLD_FRENCH}]" << std::endl;
@@ -111,7 +130,7 @@ class CppHelloWorldApp extends IncrementalHelloWorldApp {
             }
         """),
         sourceFile("cpp", "sum.cpp", """
-            #include "hello.h"
+            #include "common.h"
 
             int DLL_FUNC sum(int a, int b) {
                 return a + b;
@@ -166,4 +185,9 @@ Running main() from gtest_main.cc
     public SourceFile getBrokenFile() {
         return sourceFile("cpp", "broken.cpp", """'broken""")
     }
+
+    @Override
+    String getSourceSetType() {
+        return "CppSourceSet"
+    }
 }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppPCHHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppPCHHelloWorldApp.groovy
deleted file mode 100644
index 9789109..0000000
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/CppPCHHelloWorldApp.groovy
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.fixtures.app
-
-import org.gradle.integtests.fixtures.SourceFile
-
-class CppPCHHelloWorldApp extends PCHHelloWorldApp {
-    @Override
-    SourceFile getMainSource() {
-        return sourceFile("cpp", "main.cpp", """
-            // Simple hello world app
-            #include <iostream>
-            #include "hello.h"
-
-            int main () {
-              Greeter greeter;
-              greeter.sayHello();
-              std::cout << sum(5, 7);
-              return 0;
-            }
-        """);
-    }
-
-    @Override
-    SourceFile getLibraryHeader() {
-        getLibraryHeader("")
-    }
-
-    @Override
-    SourceFile getLibraryHeader(String path) {
-        return sourceFile("headers/${path}", "hello.h", """
-            #ifndef HELLO_H
-            #define HELLO_H
-            #ifdef _WIN32
-            #define DLL_FUNC __declspec(dllexport)
-            #else
-            #define DLL_FUNC
-            #endif
-
-            class Greeter {
-                public:
-                void DLL_FUNC sayHello();
-            };
-
-            int DLL_FUNC sum(int a, int b);
-            #ifdef FRENCH
-            #pragma message("<==== compiling bonjour.h ====>")
-            #else
-            #pragma message("<==== compiling hello.h ====>")
-            #endif
-            #endif
-        """);
-    }
-
-    @Override
-    TestApp getAlternate() {
-        return new TestApp() {
-            @Override
-            SourceFile getMainSource() {
-                return getAlternateMainSource()
-            }
-
-            @Override
-            SourceFile getLibraryHeader() {
-                return sourceFile("headers", "hello.h", """
-                    #ifndef HELLO_H
-                    #define HELLO_H
-                    #ifdef _WIN32
-                    #define DLL_FUNC __declspec(dllexport)
-                    #else
-                    #define DLL_FUNC
-                    #endif
-
-                    class Greeter {
-                        public:
-                        void DLL_FUNC sayHello();
-                    };
-
-                    int DLL_FUNC sum(int a, int b);
-                    #pragma message("<==== compiling althello.h ====>")
-                    #endif
-                """);
-            }
-
-            @Override
-            List<SourceFile> getLibrarySources() {
-                return getAlternateLibrarySources()
-            }
-        }
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources() {
-        return getLibrarySources("")
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources(String path) {
-        return [
-                sourceFile("cpp", "hello.cpp", """
-                    #include "${path}hello.h"
-                    #include <iostream>
-
-                    #ifdef FRENCH
-                    const char* greeting() {
-                        return "${HELLO_WORLD_FRENCH}";
-                    }
-                    #endif
-
-                    void DLL_FUNC Greeter::sayHello() {
-                        #ifdef FRENCH
-                        std::cout << greeting() << std::endl;
-                        #else
-                        std::cout << "${HELLO_WORLD}" << std::endl;
-                        #endif
-                    }
-                """),
-                sourceFile("cpp", "sum.cpp", """
-                    #include "${path}hello.h"
-
-                    int DLL_FUNC sum(int a, int b) {
-                        return a + b;
-                    }
-                """)
-        ]
-    }
-
-    @Override
-    SourceFile getSystemHeader() {
-        return getSystemHeader("")
-    }
-
-    @Override
-    SourceFile getSystemHeader(String path) {
-        sourceFile("headers/${path}", "systemHeader.h", """
-            #ifndef SYSTEMHEADER_H
-            #define SYSTEMHEADER_H
-            #ifdef _WIN32
-            #define DLL_FUNC __declspec(dllexport)
-            #else
-            #define DLL_FUNC
-            #endif
-            void DLL_FUNC systemCall();
-            #pragma message("<==== compiling systemHeader.h ====>")
-            #endif
-        """)
-    }
-
-    @Override
-    String getIOHeader() {
-        return "iostream"
-    }
-
-    @Override
-    SourceFile getAlternateMainSource() {
-        return getMainSource()
-    }
-
-    @Override
-    String getAlternateOutput() {
-        return null
-    }
-
-    @Override
-    List<SourceFile> getAlternateLibrarySources() {
-        return getLibrarySources()
-    }
-
-    @Override
-    String getAlternateLibraryOutput() {
-        return null
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/IncrementalHelloWorldApp.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/IncrementalHelloWorldApp.java
index b9e0c42..887400e 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/IncrementalHelloWorldApp.java
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/IncrementalHelloWorldApp.java
@@ -20,7 +20,7 @@ import org.gradle.integtests.fixtures.SourceFile;
 
 import java.util.List;
 
-public abstract class IncrementalHelloWorldApp extends HelloWorldApp {
+public abstract class IncrementalHelloWorldApp extends CommonHeaderHelloWorldApp {
     public TestApp getAlternate() {
         return new TestApp() {
             @Override
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/MixedLanguageHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/MixedLanguageHelloWorldApp.groovy
index 13f69a2..f775854 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/MixedLanguageHelloWorldApp.groovy
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/MixedLanguageHelloWorldApp.groovy
@@ -39,9 +39,9 @@ public class MixedLanguageHelloWorldApp extends HelloWorldApp {
                         architecture "i386"
                     }
                 }
-            }
-            componentSpecs.all {
-                it.targetPlatform "x86"
+                components {
+                    all { it.targetPlatform "x86" }
+                }
             }
 """
     }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCHelloWorldApp.groovy
index 98e4cdd..5079568 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCHelloWorldApp.groovy
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCHelloWorldApp.groovy
@@ -57,6 +57,8 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
     @Override
     SourceFile getLibraryHeader() {
         return sourceFile("headers", "hello.h", """
+            #ifndef HELLO_H
+            #define HELLO_H
             #import <Foundation/Foundation.h>
 
             @interface Greeter : NSObject
@@ -64,14 +66,33 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
             @end
 
             int sum(int a, int b);
+
+            #ifdef FRENCH
+            #pragma message("<==== compiling bonjour.h ====>")
+            #else
+            #pragma message("<==== compiling hello.h ====>")
+            #endif
+
+            #endif
         """);
     }
 
     @Override
+    def SourceFile getCommonHeader() {
+        sourceFile("headers", "common.h", """
+            #ifndef COMMON_H
+            #define COMMON_H
+            #include "hello.h"
+            #include <stdio.h>
+            #endif
+        """)
+    }
+
+    @Override
     List<SourceFile> getLibrarySources() {
         return [
                 sourceFile("objc", "hello.m", """
-            #import "hello.h"
+            #include "common.h"
 
             @implementation Greeter
             - (void) sayHello {
@@ -84,7 +105,7 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
             @end
         """),
                 sourceFile("objc", "sum.m", """
-            #import "hello.h"
+            #include "common.h"
 
             int sum (int a, int b)
             {
@@ -97,7 +118,7 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
     List<SourceFile> getAlternateLibrarySources() {
         return [
                 sourceFile("objc", "hello.m", """
-            #import "hello.h"
+            #include "common.h"
 
             @implementation Greeter
             - (void) sayHello {
@@ -112,9 +133,9 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
             }
         """),
                 sourceFile("objc", "sum.m", """
-            #import "hello.h"
+            #include "common.h"
 
-            int sum (int a, int b)
+            int sum(int a, int b)
             {
                 return a + b;
             }
@@ -144,4 +165,9 @@ class ObjectiveCHelloWorldApp extends IncrementalHelloWorldApp {
     public SourceFile getBrokenFile() {
         return sourceFile("objc", "broken.m", """'broken""")
     }
+
+    @Override
+    String getSourceSetType() {
+        return "ObjectiveCSourceSet"
+    }
 }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCPCHHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCPCHHelloWorldApp.groovy
deleted file mode 100644
index f65850d..0000000
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCPCHHelloWorldApp.groovy
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.fixtures.app
-
-import org.gradle.integtests.fixtures.SourceFile
-
-class ObjectiveCPCHHelloWorldApp extends PCHHelloWorldApp {
-
-    @Override
-    SourceFile getMainSource() {
-        return sourceFile("objc", "main.m", """
-            // Simple hello world app
-            #include "hello.h"
-
-            int main(int argc, const char * argv[])
-            {
-                Greeter* greeter = [Greeter new];
-                [greeter sayHello];
-                [greeter release];
-                printf("%d", sum(7, 5));
-                return 0;
-            }
-        """);
-    }
-
-    @Override
-    SourceFile getLibraryHeader() {
-        return getLibraryHeader("")
-    }
-
-    @Override
-    SourceFile getLibraryHeader(String path) {
-        return sourceFile("headers/${path}", "hello.h", """
-            #ifndef HELLO_H
-            #define HELLO_H
-            #import <Foundation/Foundation.h>
-
-            @interface Greeter : NSObject
-                - (void)sayHello;
-            @end
-
-            int sum(int a, int b);
-
-            #ifdef FRENCH
-            #pragma message("<==== compiling bonjour.h ====>")
-            #else
-            #pragma message("<==== compiling hello.h ====>")
-            #endif
-            #endif
-        """);
-    }
-
-    @Override
-    TestApp getAlternate() {
-        return new TestApp() {
-            @Override
-            SourceFile getMainSource() {
-                return getAlternateMainSource()
-            }
-
-            @Override
-            SourceFile getLibraryHeader() {
-                return sourceFile("headers", "hello.h", """
-                #ifndef HELLO_H
-                #define HELLO_H
-                #import <Foundation/Foundation.h>
-
-                @interface Greeter : NSObject
-                    - (void)sayHello;
-                @end
-
-                int sum(int a, int b);
-
-                #pragma message("<==== compiling althello.h ====>")
-                #endif
-            """);
-            }
-
-            @Override
-            List<SourceFile> getLibrarySources() {
-                return getAlternateLibrarySources()
-            }
-        }
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources() {
-        return getLibrarySources("")
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources(String path) {
-        return [
-                sourceFile("objc", "hello.m", """
-            #import "${path}hello.h"
-            #import <stdio.h>
-
-            @implementation Greeter
-            - (void) sayHello {
-                NSString *helloWorld = @"${HELLO_WORLD}";
-                #ifdef FRENCH
-                helloWorld = @"${HELLO_WORLD_FRENCH}";
-                #endif
-                fprintf(stdout, "%s\\n", [helloWorld UTF8String]);
-            }
-            @end
-        """),
-                sourceFile("objc", "sum.m", """
-            #import "${path}hello.h"
-
-            int sum (int a, int b)
-            {
-                return a + b;
-            }
-        """)]
-    }
-
-    @Override
-    SourceFile getSystemHeader() {
-        return getSystemHeader("")
-    }
-
-    @Override
-    SourceFile getSystemHeader(String path) {
-        sourceFile("headers/${path}", "systemHeader.h", """
-            #ifndef SYSTEMHEADER_H
-            #define SYSTEMHEADER_H
-            void systemCall();
-            #pragma message("<==== compiling systemHeader.h ====>")
-            #endif
-        """)
-    }
-
-    @Override
-    String getIOHeader() {
-        return "stdio.h"
-    }
-
-    @Override
-    SourceFile getAlternateMainSource() {
-        return getMainSource()
-    }
-
-    @Override
-    String getAlternateOutput() {
-        return null
-    }
-
-    @Override
-    List<SourceFile> getAlternateLibrarySources() {
-        return getAlternateLibrarySources()
-    }
-
-    @Override
-    String getAlternateLibraryOutput() {
-        return null
-    }
-
-    public String getExtraConfiguration(String binaryName = null) {
-        return """
-            binaries.matching { ${binaryName ? "it.name == '$binaryName'" : "true"} }.all {
-                if (targetPlatform.operatingSystem.macOsX) {
-                    linker.args "-framework", "Foundation"
-                } else {
-                    objcCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
-                    linker.args "-lgnustep-base", "-lobjc"
-                }
-            }
-        """
-    }
-
-    @Override
-    List<String> getPluginList() {
-        ['objective-c']
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppHelloWorldApp.groovy
index dc55678..176f0e1 100644
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppHelloWorldApp.groovy
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppHelloWorldApp.groovy
@@ -40,6 +40,8 @@ class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
     @Override
     SourceFile getLibraryHeader() {
         return sourceFile("headers", "hello.h", """
+            #ifndef HELLO_H
+            #define HELLO_H
             #ifdef _WIN32
             #define DLL_FUNC __declspec(dllexport)
             #else
@@ -48,14 +50,33 @@ class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
 
             void DLL_FUNC sayHello();
             int DLL_FUNC sum(int a, int b);
+
+            #ifdef FRENCH
+            #pragma message("<==== compiling bonjour.h ====>")
+            #else
+            #pragma message("<==== compiling hello.h ====>")
+            #endif
+
+            #endif
         """);
     }
 
+    @Override
+    def SourceFile getCommonHeader() {
+        sourceFile("headers", "common.h", """
+            #ifndef COMMON_H
+            #define COMMON_H
+            #include "hello.h"
+            #include <iostream>
+            #endif
+        """)
+    }
+
     List<SourceFile> librarySources = [
             sourceFile("objcpp", "hello.mm", """
             #define __STDC_LIMIT_MACROS
+            #include "common.h"
             #include <stdint.h>
-            #include "hello.h"
             #import <Foundation/Foundation.h>
 
             #include <iostream>
@@ -78,7 +99,7 @@ class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
             }
         """),
             sourceFile("objcpp", "sum.mm", """
-            #include "hello.h"
+            #include "common.h"
             int DLL_FUNC sum(int a, int b) {
                 return a + b;
             }
@@ -111,16 +132,16 @@ class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
         return [
             sourceFile("objcpp", "hello.mm", """
             #define __STDC_LIMIT_MACROS
+            #include "common.h"
             #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"
+            #include "common.h"
             int DLL_FUNC sum(int a, int b) {
                 return a + b;
             }
@@ -150,4 +171,9 @@ class ObjectiveCppHelloWorldApp extends IncrementalHelloWorldApp {
     public SourceFile getBrokenFile() {
         return sourceFile("objcpp", "broken.mm", """'broken""")
     }
+
+    @Override
+    String getSourceSetType() {
+        return "ObjectiveCppSourceSet"
+    }
 }
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppPCHHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppPCHHelloWorldApp.groovy
deleted file mode 100644
index b46c7ab..0000000
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/ObjectiveCppPCHHelloWorldApp.groovy
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.fixtures.app
-
-import org.gradle.integtests.fixtures.SourceFile
-
-class ObjectiveCppPCHHelloWorldApp extends PCHHelloWorldApp {
-    @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() {
-        getLibraryHeader("")
-    }
-    @Override
-    SourceFile getLibraryHeader(String path) {
-        return sourceFile("headers/${path}", "hello.h", """
-            #ifndef HELLO_H
-            #define HELLO_H
-            void sayHello();
-            int sum(int a, int b);
-            #ifdef FRENCH
-            #pragma message("<==== compiling bonjour.h ====>")
-            #else
-            #pragma message("<==== compiling hello.h ====>")
-            #endif
-            #endif
-        """);
-    }
-
-    @Override
-    TestApp getAlternate() {
-        return new TestApp() {
-            @Override
-            SourceFile getMainSource() {
-                return getAlternateMainSource()
-            }
-
-            @Override
-            SourceFile getLibraryHeader() {
-                return sourceFile("headers", "hello.h", """
-                    #ifndef HELLO_H
-                    #define HELLO_H
-                    void sayHello();
-                    int sum(int a, int b);
-
-                    #pragma message("<==== compiling althello.h ====>")
-                    #endif
-                """);
-            }
-
-            @Override
-            List<SourceFile> getLibrarySources() {
-                return getAlternateLibrarySources()
-            }
-        }
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources() {
-        return getLibrarySources("")
-    }
-
-    @Override
-    List<SourceFile> getLibrarySources(String path) {
-        return [
-                sourceFile("objcpp", "hello.mm", """
-                    #define __STDC_LIMIT_MACROS
-                    #include "${path}hello.h"
-                    #include <iostream>
-                    #include <stdint.h>
-                    #import <Foundation/Foundation.h>
-
-                    #ifdef FRENCH
-                    const char* greeting() {
-                        return "${HELLO_WORLD_FRENCH}";
-                    }
-                    #endif
-
-                    void 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 "${path}hello.h"
-                    int sum(int a, int b) {
-                        return a + b;
-                    }
-                """)
-        ]
-    }
-
-    @Override
-    SourceFile getSystemHeader() {
-        return getSystemHeader("")
-    }
-
-    @Override
-    SourceFile getSystemHeader(String path) {
-        sourceFile("headers/${path}", "systemHeader.h", """
-            #ifndef SYSTEMHEADER_H
-            #define SYSTEMHEADER_H
-            void systemCall();
-            #pragma message("<==== compiling systemHeader.h ====>")
-            #endif
-        """)
-    }
-
-    @Override
-    String getIOHeader() {
-        return "iostream"
-    }
-
-    @Override
-    SourceFile getAlternateMainSource() {
-        return getMainSource()
-    }
-
-    @Override
-    String getAlternateOutput() {
-        return null
-    }
-
-    @Override
-    List<SourceFile> getAlternateLibrarySources() {
-        return getLibrarySources()
-    }
-
-    @Override
-    String getAlternateLibraryOutput() {
-        return null
-    }
-
-    public String getExtraConfiguration(String binaryName = null) {
-        return """
-            binaries.matching { ${binaryName ? "it.name == '$binaryName'" : "true"} }.all {
-                if (targetPlatform.operatingSystem.macOsX) {
-                    linker.args "-framework", "Foundation"
-                } else {
-                    objcppCompiler.args "-I/usr/include/GNUstep", "-I/usr/local/include/objc", "-fconstant-string-class=NSConstantString", "-D_NATIVE_OBJC_EXCEPTIONS"
-                    linker.args "-lgnustep-base", "-lobjc"
-                }
-            }
-        """
-    }
-
-    @Override
-    List<String> getPluginList() {
-        ['objective-cpp']
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/PCHHelloWorldApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/PCHHelloWorldApp.groovy
deleted file mode 100644
index c454bc9..0000000
--- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/PCHHelloWorldApp.groovy
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.nativeplatform.fixtures.app
-
-import org.gradle.integtests.fixtures.SourceFile
-
-abstract class PCHHelloWorldApp extends IncrementalHelloWorldApp {
-    abstract SourceFile getLibraryHeader(String path)
-    abstract List<SourceFile> getLibrarySources(String path)
-    abstract SourceFile getSystemHeader()
-    abstract SourceFile getSystemHeader(String path)
-    abstract String getIOHeader()
-
-    public SourceFile getBrokenFile() {
-        return null
-    }
-}
diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/internal/configure/TestNativeBinariesFactory.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/internal/configure/TestNativeBinariesFactory.java
new file mode 100644
index 0000000..702fb1c
--- /dev/null
+++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/internal/configure/TestNativeBinariesFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.nativeplatform.internal.configure;
+
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.nativeplatform.BuildType;
+import org.gradle.nativeplatform.Flavor;
+import org.gradle.nativeplatform.NativeBinarySpec;
+import org.gradle.nativeplatform.NativeComponentSpec;
+import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativeplatform.platform.NativePlatform;
+import org.gradle.platform.base.binary.BaseBinarySpec;
+import org.gradle.platform.base.internal.BinaryNamingScheme;
+import org.gradle.platform.base.internal.ComponentSpecInternal;
+
+public class TestNativeBinariesFactory {
+
+    public static <T extends BaseBinarySpec & NativeBinarySpec> T create(Class<T> type, String name, Instantiator instantiator, ITaskFactory taskFactory, final NativeComponentSpec component,
+        final BinaryNamingScheme namingScheme, final NativeDependencyResolver resolver,
+        final NativePlatform platform, final BuildType buildType, final Flavor flavor) {
+        T binary = BaseBinarySpec.create(type, name, instantiator, taskFactory);
+        NativeBinaries.initialize(binary, namingScheme, resolver, platform, buildType, flavor);
+        ComponentSpecInternal componentInternal = (ComponentSpecInternal) component;
+        binary.setBinarySources(componentInternal.getSources().copy(name));
+        return binary;
+    }
+}
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/integtest/PlayPlatformIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/integtest/PlayPlatformIntegrationTest.groovy
index 5f1d226..4b83321 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/integtest/PlayPlatformIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/integtest/PlayPlatformIntegrationTest.groovy
@@ -126,6 +126,25 @@ model {
         failure.assertHasCause "Not a supported Scala platform identifier X. Supported values are: ['2.10', '2.11']."
     }
 
+    def "fails when trying to build for multiple play platforms"() {
+        when:
+        buildFile << """
+model {
+    components {
+        play {
+            platform play: '2.2.4'
+            platform play: '2.3.6'
+        }
+    }
+}
+"""
+        then:
+        fails "assemble"
+
+        and:
+        failure.assertHasCause "Multiple target platforms for 'PlayApplicationSpec' is not (yet) supported."
+    }
+
     JarTestFixture jar(String fileName) {
         new JarTestFixture(file(fileName))
     }
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayApplicationPluginIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayApplicationPluginIntegrationTest.groovy
index e78e6a2..423168b 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayApplicationPluginIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayApplicationPluginIntegrationTest.groovy
@@ -58,18 +58,18 @@ Play Application 'play'
 
 Source sets
     Java source 'play:java'
-        app
+        srcDir: app
         includes: **/*.java
     JVM resources 'play:resources'
-        conf
+        srcDir: conf
     Routes source 'play:routesSources'
-        conf
+        srcDir: conf
         includes: routes, *.routes
     Scala source 'play:scala'
-        app
+        srcDir: app
         includes: **/*.scala
     Twirl template source 'play:twirlTemplates'
-        app
+        srcDir: app
         includes: **/*.html
 
 Binaries
@@ -142,25 +142,8 @@ Binaries
 
         then:
         output.contains(TextUtil.toPlatformLineSeparators("""
-Play Application 'play'
------------------------
-
-Source sets
     ${StringUtils.capitalize(languageName)} source 'play:extra'
-        src${File.separator}extra
-    Java source 'play:java'
-        app
-        includes: **/*.java
-    JVM resources 'play:resources'
-        conf
-    Routes source 'play:routesSources'
-        conf
-        includes: routes, *.routes
-    Scala source 'play:scala'
-        app
-        includes: **/*.scala
-    Twirl template source 'play:twirlTemplates'
-        app
+        srcDir: src${File.separator}extra
 """))
 
         when:
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginIntegrationTest.groovy
index a220ce0..831e50b 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginIntegrationTest.groovy
@@ -50,11 +50,11 @@ class PlayCoffeeScriptPluginIntegrationTest extends AbstractIntegrationSpec {
         then:
         normalizedOutput.contains("""
     CoffeeScript source 'play:coffeeScriptAssets'
-        app/assets
+        srcDir: app/assets
         includes: **/*.coffee""")
         normalizedOutput.contains("""
     CoffeeScript source 'play:otherCoffeeScript'
-        src/play/otherCoffeeScript
+        srcDir: src/play/otherCoffeeScript
 """)
     }
 
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayJavaScriptPluginIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayJavaScriptPluginIntegrationTest.groovy
index 06b9750..321531d 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayJavaScriptPluginIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/plugins/PlayJavaScriptPluginIntegrationTest.groovy
@@ -46,10 +46,10 @@ class PlayJavaScriptPluginIntegrationTest extends AbstractIntegrationSpec {
         then:
         normalizedOutput.contains("""
     JavaScript source 'play:javaScriptAssets'
-        app/assets
+        srcDir: app/assets
         includes: **/*.js
     JavaScript source 'play:otherJavaScript'
-        src/play/otherJavaScript
+        srcDir: src/play/otherJavaScript
 """)
     }
 
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/CustomCoffeeScriptImplementationIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/CustomCoffeeScriptImplementationIntegrationTest.groovy
index 856a478..d138aed 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/CustomCoffeeScriptImplementationIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/CustomCoffeeScriptImplementationIntegrationTest.groovy
@@ -38,6 +38,10 @@ class CustomCoffeeScriptImplementationIntegrationTest extends AbstractCoffeeScri
 
             repositories{
                 jcenter()
+                maven {
+                    name = "gradle-js"
+                    url = "https://repo.gradle.org/gradle/javascript-public"
+                }
             }
         """
     }
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/RoutesCompileIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/RoutesCompileIntegrationTest.groovy
index b0bb07c..8733e06 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/RoutesCompileIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/RoutesCompileIntegrationTest.groovy
@@ -62,49 +62,59 @@ repositories{
         destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala")
     }
 
-    def "runs compiler multiple times"(){
-        when:
-        withRoutesTemplate()
-        then:
+    def "recompiles on changed routes file input"() {
+        given:
+        TestFile templateFile = withRoutesTemplate()
         succeeds("routesCompileRoutesSourcesPlayBinary")
+
         and:
         destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala")
+        def routesFirstCompileSnapshot = file(destinationDirPath, "controllers/routes.java").snapshot();
+        def revRoutingFirstCompileSnapshot = file(destinationDirPath, "routes_reverseRouting.scala").snapshot();
+        def routingFirstCompileSnapshot = file(destinationDirPath, "routes_routing.scala").snapshot();
 
-        withRoutesTemplate("foo")
+        when:
+        templateFile << "\n\n"
         and:
-        succeeds("routesCompileRoutesSourcesPlayBinary")
+        succeeds "routesCompileRoutesSourcesPlayBinary"
+
         then:
-        destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala",
-                "controllers/foo/routes.java", "foo/routes_reverseRouting.scala", "foo/routes_routing.scala")
+        executedAndNotSkipped ":routesCompileRoutesSourcesPlayBinary"
+
+        and:
+        file(destinationDirPath, "controllers/routes.java").assertHasChangedSince(routesFirstCompileSnapshot)
+        file(destinationDirPath, "routes_reverseRouting.scala").assertHasChangedSince(revRoutingFirstCompileSnapshot);
+        file(destinationDirPath, "routes_routing.scala").assertHasChangedSince(routingFirstCompileSnapshot);
 
         when:
-        file("conf/foo.routes").delete()
+        succeeds "routesCompileRoutesSourcesPlayBinary"
+
         then:
-        succeeds("routesCompileRoutesSourcesPlayBinary")
-        and:
-        destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala")
+        skipped ":routesCompileRoutesSourcesPlayBinary"
     }
 
-    def "removes stale output files in incremental compile"(){
-        given:
-        TestFile templateFile = withRoutesTemplate()
+    def "compiles additional routes file and cleans up output on removal"(){
+        when:
+        withRoutesTemplate()
+        then:
         succeeds("routesCompileRoutesSourcesPlayBinary")
-
         and:
         destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala")
-        def routesFirstCompileSnapshot = file(destinationDirPath, "controllers/routes.java").snapshot();
-        def revRoutingFirstCompileSnapshot = file(destinationDirPath, "routes_reverseRouting.scala").snapshot();
-        def routingFirstCompileSnapshot = file(destinationDirPath, "routes_routing.scala").snapshot();
 
         when:
-        templateFile.delete()
+        withRoutesTemplate("foo")
+        and:
+        succeeds("routesCompileRoutesSourcesPlayBinary")
+        then:
+        destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala",
+                "controllers/foo/routes.java", "foo/routes_reverseRouting.scala", "foo/routes_routing.scala")
 
+        when:
+        file("conf/foo.routes").delete()
         then:
         succeeds("routesCompileRoutesSourcesPlayBinary")
         and:
-        file(destinationDirPath, "controllers/routes.java").assertHasNotChangedSince(routesFirstCompileSnapshot);
-        file(destinationDirPath, "routes_reverseRouting.scala").assertHasNotChangedSince(revRoutingFirstCompileSnapshot);
-        file(destinationDirPath, "routes_routing.scala").assertHasNotChangedSince(routingFirstCompileSnapshot);
+        destinationDir.assertHasDescendants("controllers/routes.java", "routes_reverseRouting.scala", "routes_routing.scala")
     }
 
     def "compiles multiple Routes source sets as part of play application build" () {
@@ -146,23 +156,23 @@ Play Application 'play'
 -----------------------
 
 Source sets
-    Routes source 'play:extraRoutes'
-        extraRoutes
     Java source 'play:java'
-        app
+        srcDir: app
         includes: **/*.java
-    Routes source 'play:otherRoutes'
-        otherRoutes
     JVM resources 'play:resources'
-        conf
+        srcDir: conf
+    Routes source 'play:extraRoutes'
+        srcDir: extraRoutes
+    Routes source 'play:otherRoutes'
+        srcDir: otherRoutes
     Routes source 'play:routesSources'
-        conf
+        srcDir: conf
         includes: routes, *.routes
     Scala source 'play:scala'
-        app
+        srcDir: app
         includes: **/*.scala
     Twirl template source 'play:twirlTemplates'
-        app
+        srcDir: app
         includes: **/*.html
 
 Binaries
diff --git a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/TwirlCompileIntegrationTest.groovy b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/TwirlCompileIntegrationTest.groovy
index 0ff0c21..746ba38 100644
--- a/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/TwirlCompileIntegrationTest.groovy
+++ b/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/TwirlCompileIntegrationTest.groovy
@@ -149,23 +149,23 @@ Play Application 'play'
 -----------------------
 
 Source sets
-    Twirl template source 'play:extraTwirl'
-        extraSources
     Java source 'play:java'
-        app
+        srcDir: app
         includes: **/*.java
-    Twirl template source 'play:otherTwirl'
-        otherSources
     JVM resources 'play:resources'
-        conf
+        srcDir: conf
     Routes source 'play:routesSources'
-        conf
+        srcDir: conf
         includes: routes, *.routes
     Scala source 'play:scala'
-        app
+        srcDir: app
         includes: **/*.scala
+    Twirl template source 'play:extraTwirl'
+        srcDir: extraSources
+    Twirl template source 'play:otherTwirl'
+        srcDir: otherSources
     Twirl template source 'play:twirlTemplates'
-        app
+        srcDir: app
         includes: **/*.html
 
 Binaries
diff --git a/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/basicplayapp/test/notATest.yaml b/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/basicplayapp/test/notATest.yaml
new file mode 100644
index 0000000..d7fb7ab
--- /dev/null
+++ b/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/basicplayapp/test/notATest.yaml
@@ -0,0 +1,7 @@
+name: Test
+number: 1234
+items:
+    - name: item1
+      description: first item
+    - name: item2
+      description: second item
diff --git a/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/playappwithdependencies/test/notATest.yaml b/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/playappwithdependencies/test/notATest.yaml
new file mode 100644
index 0000000..d7fb7ab
--- /dev/null
+++ b/subprojects/platform-play/src/integTest/resources/org/gradle/play/integtest/fixtures/app/playappwithdependencies/test/notATest.yaml
@@ -0,0 +1,7 @@
+name: Test
+number: 1234
+items:
+    - name: item1
+      description: first item
+    - name: item2
+      description: second item
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/internal/distribution/DefaultPlayDistributionContainer.java b/subprojects/platform-play/src/main/java/org/gradle/play/internal/distribution/DefaultPlayDistributionContainer.java
index 0254261..ca705ba 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/internal/distribution/DefaultPlayDistributionContainer.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/internal/distribution/DefaultPlayDistributionContainer.java
@@ -18,7 +18,7 @@ package org.gradle.play.internal.distribution;
 
 import org.gradle.api.distribution.Distribution;
 import org.gradle.internal.reflect.Instantiator;
-import org.gradle.platform.base.internal.rules.RuleAwarePolymorphicDomainObjectContainer;
+import org.gradle.api.internal.rules.RuleAwarePolymorphicDomainObjectContainer;
 import org.gradle.play.distribution.PlayDistributionContainer;
 
 public class DefaultPlayDistributionContainer extends RuleAwarePolymorphicDomainObjectContainer<Distribution> implements PlayDistributionContainer {
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayApplicationPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayApplicationPlugin.java
index c2cfbca..2372f80 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayApplicationPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayApplicationPlugin.java
@@ -18,10 +18,8 @@ package org.gradle.play.plugins;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.*;
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.internal.file.DefaultSourceDirectorySet;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.copy.CopySpecInternal;
-import org.gradle.api.internal.java.DefaultJvmResourceSet;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.tasks.scala.IncrementalCompileOptions;
@@ -30,11 +28,13 @@ import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.jvm.tasks.Jar;
 import org.gradle.language.base.FunctionalSourceSet;
 import org.gradle.language.base.LanguageSourceSet;
+import org.gradle.language.base.internal.compile.Compiler;
 import org.gradle.language.base.sources.BaseLanguageSourceSet;
 import org.gradle.language.java.JavaSourceSet;
 import org.gradle.language.java.internal.DefaultJavaLanguageSourceSet;
 import org.gradle.language.java.plugins.JavaLanguagePlugin;
 import org.gradle.language.jvm.JvmResourceSet;
+import org.gradle.language.jvm.internal.DefaultJvmResourceLanguageSourceSet;
 import org.gradle.language.routes.RoutesSourceSet;
 import org.gradle.language.routes.internal.DefaultRoutesSourceSet;
 import org.gradle.language.scala.ScalaLanguageSourceSet;
@@ -44,7 +44,6 @@ import org.gradle.language.scala.tasks.PlatformScalaCompile;
 import org.gradle.language.twirl.TwirlSourceSet;
 import org.gradle.language.twirl.internal.DefaultTwirlSourceSet;
 import org.gradle.model.*;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.platform.base.*;
 import org.gradle.platform.base.internal.ComponentSpecInternal;
 import org.gradle.platform.base.internal.DefaultPlatformRequirement;
@@ -66,14 +65,13 @@ import org.gradle.play.platform.PlayPlatform;
 import org.gradle.play.tasks.PlayRun;
 import org.gradle.play.tasks.RoutesCompile;
 import org.gradle.play.tasks.TwirlCompile;
-import org.gradle.language.base.internal.compile.Compiler;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Date;
 
 /**
- * Plugin for Play Framework component support. Registers the {@link org.gradle.play.PlayApplicationSpec} component type for the {@link org.gradle.platform.base.ComponentSpecContainer}.
+ * Plugin for Play Framework component support. Registers the {@link org.gradle.play.PlayApplicationSpec} component type for the components container.
  */
 
 @Incubating
@@ -111,7 +109,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void createDefaultPlayApp(CollectionBuilder<PlayApplicationSpec> builder) {
+        void createDefaultPlayApp(ModelMap<PlayApplicationSpec> builder) {
             builder.create("play");
         }
 
@@ -133,10 +131,10 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void configureDefaultPlaySources(ComponentSpecContainer components, ServiceRegistry serviceRegistry) {
+        void configureDefaultPlaySources(ModelMap<PlayApplicationSpec> playApplicationComponents, ServiceRegistry serviceRegistry) {
             final FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
             final Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            components.withType(PlayApplicationSpec.class).all(new Action<PlayApplicationSpec>() {
+            playApplicationComponents.all(new Action<PlayApplicationSpec>() {
                 public void execute(PlayApplicationSpec playComponent) {
                     // TODO:DAZ Scala source set type should be registered via scala-lang plugin
                     ScalaLanguageSourceSet scalaSources = BaseLanguageSourceSet.create(DefaultScalaLanguageSourceSet.class, "scala", playComponent.getName(), fileResolver, instantiator);
@@ -153,8 +151,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
                     javaSources.getSource().include("**/*.java");
                     sources.add(javaSources);
 
-                    DefaultSourceDirectorySet resourcesDirectorySet = new DefaultSourceDirectorySet("resources", fileResolver);
-                    JvmResourceSet appResources = instantiator.newInstance(DefaultJvmResourceSet.class, "resources", playComponent.getName(), resourcesDirectorySet);
+                    JvmResourceSet appResources = BaseLanguageSourceSet.create(DefaultJvmResourceLanguageSourceSet.class, "resources", playComponent.getName(), fileResolver, instantiator);
                     appResources.getSource().srcDirs("conf");
                     sources.add(appResources);
                 }
@@ -162,23 +159,26 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Validate
-        void failOnMultiplePlayComponents(CollectionBuilder<PlayApplicationSpec> container) {
+        void failOnMultiplePlayComponents(ModelMap<PlayApplicationSpec> container) {
             if (container.size() >= 2) {
                 throw new GradleException("Multiple components of type 'PlayApplicationSpec' are not supported.");
             }
         }
 
-        @Finalize
-        void failOnMultipleTargetPlatforms(ComponentSpecContainer container) {
-            for (PlayApplicationSpecInternal playApplicationSpec : container.withType(PlayApplicationSpecInternal.class)) {
-                if (playApplicationSpec.getTargetPlatforms().size() > 1) {
-                    throw new GradleException("Multiple target platforms for 'PlayApplicationSpec' is not (yet) supported.");
+        @Validate
+        void failOnMultipleTargetPlatforms(ModelMap<PlayApplicationSpec> playApplications) {
+            playApplications.afterEach(new Action<PlayApplicationSpec>() {
+                public void execute(PlayApplicationSpec playApplication) {
+                    PlayApplicationSpecInternal playApplicationInternal = (PlayApplicationSpecInternal) playApplication;
+                    if (playApplicationInternal.getTargetPlatforms().size() > 1) {
+                        throw new GradleException("Multiple target platforms for 'PlayApplicationSpec' is not (yet) supported.");
+                    }
                 }
-            }
+            });
         }
 
         @ComponentBinaries
-        void createBinaries(CollectionBuilder<PlayApplicationBinarySpec> binaries, final PlayApplicationSpec componentSpec,
+        void createBinaries(ModelMap<PlayApplicationBinarySpec> binaries, final PlayApplicationSpec componentSpec,
                             final PlatformResolvers platforms, final PlayPluginConfigurations configurations, final ServiceRegistry serviceRegistry,
                             @Path("buildDir") final File buildDir, final ProjectIdentifier projectIdentifier) {
 
@@ -210,8 +210,8 @@ public class PlayApplicationPlugin implements Plugin<Project> {
                     JvmClasses classes = playBinary.getClasses();
                     classes.setClassesDir(new File(binaryBuildDir, "classes"));
 
-                    DomainObjectSet<JvmResourceSet> jvmResourceSets = componentSpec.getSource().withType(JvmResourceSet.class);
-                    for (JvmResourceSet jvmResourceSet : jvmResourceSets) {
+                    ModelMap<JvmResourceSet> jvmResourceSets = componentSpec.getSource().withType(JvmResourceSet.class);
+                    for (JvmResourceSet jvmResourceSet : jvmResourceSets.values()) {
                         for (File resourceDir : jvmResourceSet.getSource()) {
                             classes.addResourceDir(resourceDir);
                         }
@@ -236,10 +236,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
                 String defaultPlayPlatform = String.format("play-%s", DEFAULT_PLAY_VERSION);
                 return DefaultPlatformRequirement.create(defaultPlayPlatform);
             }
-            if (playApplicationSpec.getTargetPlatforms().size() == 1) {
-                return playApplicationSpec.getTargetPlatforms().get(0);
-            }
-            throw new InvalidUserDataException("Play application can only target a single platform");
+            return playApplicationSpec.getTargetPlatforms().get(0);
         }
 
         private void initialiseConfigurations(PlayPluginConfigurations configurations, PlayPlatform playPlatform) {
@@ -249,7 +246,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void createTwirlSourceSets(CollectionBuilder<PlayApplicationSpec> components) {
+        void createTwirlSourceSets(ModelMap<PlayApplicationSpec> components) {
             components.beforeEach(new Action<PlayApplicationSpec>() {
                 @Override
                 public void execute(PlayApplicationSpec playComponent) {
@@ -261,7 +258,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void createRoutesSourceSets(CollectionBuilder<PlayApplicationSpec> components) {
+        void createRoutesSourceSets(ModelMap<PlayApplicationSpec> components) {
             components.beforeEach(new Action<PlayApplicationSpec>() {
                 @Override
                 public void execute(PlayApplicationSpec playComponent) {
@@ -274,12 +271,12 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void createGeneratedScalaSourceSets(CollectionBuilder<PlayApplicationBinarySpec> binaries, final ServiceRegistry serviceRegistry) {
+        void createGeneratedScalaSourceSets(ModelMap<PlayApplicationBinarySpec> binaries, final ServiceRegistry serviceRegistry) {
             createGeneratedScalaSourceSetsForType(TwirlSourceSet.class, binaries, serviceRegistry);
             createGeneratedScalaSourceSetsForType(RoutesSourceSet.class, binaries, serviceRegistry);
         }
 
-        void createGeneratedScalaSourceSetsForType(final Class<? extends LanguageSourceSet> languageSourceSetType, CollectionBuilder<PlayApplicationBinarySpec> binaries, ServiceRegistry serviceRegistry) {
+        void createGeneratedScalaSourceSetsForType(final Class<? extends LanguageSourceSet> languageSourceSetType, ModelMap<PlayApplicationBinarySpec> binaries, ServiceRegistry serviceRegistry) {
             final FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
             final Instantiator instantiator = serviceRegistry.get(Instantiator.class);
             binaries.all(new Action<PlayApplicationBinarySpec>() {
@@ -294,7 +291,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @BinaryTasks
-        void createTwirlCompileTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
+        void createTwirlCompileTasks(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
             final ToolResolver toolResolver = serviceRegistry.get(ToolResolver.class);
             final ResolvedTool<Compiler<TwirlCompileSpec>> compilerTool = toolResolver.resolveCompiler(TwirlCompileSpec.class, binary.getTargetPlatform());
             for (final TwirlSourceSet twirlSourceSet : binary.getSource().withType(TwirlSourceSet.class)) {
@@ -317,7 +314,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @BinaryTasks
-        void createRoutesCompileTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
+        void createRoutesCompileTasks(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
             final ToolResolver toolResolver = serviceRegistry.get(ToolResolver.class);
             final ResolvedTool<Compiler<RoutesCompileSpec>> compilerTool = toolResolver.resolveCompiler(RoutesCompileSpec.class, binary.getTargetPlatform());
             for (final RoutesSourceSet routesSourceSet : binary.getSource().withType(RoutesSourceSet.class)) {
@@ -340,7 +337,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @BinaryTasks
-        void createScalaCompileTask(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, @Path("buildDir") final File buildDir) {
+        void createScalaCompileTask(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary, @Path("buildDir") final File buildDir) {
             final String scalaCompileTaskName = String.format("scalaCompile%s", StringUtils.capitalize(binary.getName()));
             tasks.create(scalaCompileTaskName, PlatformScalaCompile.class, new Action<PlatformScalaCompile>() {
                 public void execute(PlatformScalaCompile scalaCompile) {
@@ -370,7 +367,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
                         scalaCompile.dependsOn(generatedSourceSet.getBuildDependencies());
                     }
 
-                    scalaCompile.setClasspath(((PlayApplicationBinarySpecInternal)binary).getClasspath());
+                    scalaCompile.setClasspath(((PlayApplicationBinarySpecInternal) binary).getClasspath());
 
                     binary.getClasses().builtBy(scalaCompile);
                 }
@@ -378,7 +375,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
         }
 
         @BinaryTasks
-        void createJarTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary) {
+        void createJarTasks(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary) {
             String jarTaskName = String.format("create%sJar", StringUtils.capitalize(binary.getName()));
             tasks.create(jarTaskName, Jar.class, new Action<Jar>() {
                 public void execute(Jar jar) {
@@ -406,7 +403,7 @@ public class PlayApplicationPlugin implements Plugin<Project> {
 
         // TODO:DAZ Need a nice way to create tasks that are associated with a binary but not part of _building_ it.
         @Mutate
-        void createPlayRunTask(CollectionBuilder<Task> tasks, BinaryContainer binaryContainer, ServiceRegistry serviceRegistry, final PlayPluginConfigurations configurations) {
+        void createPlayRunTask(ModelMap<Task> tasks, BinaryContainer binaryContainer, ServiceRegistry serviceRegistry, final PlayPluginConfigurations configurations) {
             ToolResolver toolResolver = serviceRegistry.get(ToolResolver.class);
             for (final PlayApplicationBinarySpecInternal binary : binaryContainer.withType(PlayApplicationBinarySpecInternal.class)) {
                 final ResolvedTool<PlayApplicationRunner> playApplicationRunnerTool = toolResolver.resolve(PlayApplicationRunner.class, binary.getTargetPlatform());
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayCoffeeScriptPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayCoffeeScriptPlugin.java
index b492159..8481c7b 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayCoffeeScriptPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayCoffeeScriptPlugin.java
@@ -28,10 +28,10 @@ import org.gradle.language.coffeescript.CoffeeScriptSourceSet;
 import org.gradle.language.coffeescript.internal.DefaultCoffeeScriptSourceSet;
 import org.gradle.language.javascript.JavaScriptSourceSet;
 import org.gradle.language.javascript.internal.DefaultJavaScriptSourceSet;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.Path;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.platform.base.BinaryTasks;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
@@ -70,7 +70,7 @@ public class PlayCoffeeScriptPlugin extends RuleSource {
     }
 
     @Mutate
-    void createCoffeeScriptSourceSets(CollectionBuilder<PlayApplicationSpec> components) {
+    void createCoffeeScriptSourceSets(ModelMap<PlayApplicationSpec> components) {
         components.beforeEach(new Action<PlayApplicationSpec>() {
             @Override
             public void execute(PlayApplicationSpec playComponent) {
@@ -83,7 +83,7 @@ public class PlayCoffeeScriptPlugin extends RuleSource {
     }
 
     @Mutate
-    void createGeneratedJavaScriptSourceSets(CollectionBuilder<PlayApplicationBinarySpec> binaries, final ServiceRegistry serviceRegistry) {
+    void createGeneratedJavaScriptSourceSets(ModelMap<PlayApplicationBinarySpec> binaries, final ServiceRegistry serviceRegistry) {
         final FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
         final Instantiator instantiator = serviceRegistry.get(Instantiator.class);
         binaries.all(new Action<PlayApplicationBinarySpec>() {
@@ -98,7 +98,7 @@ public class PlayCoffeeScriptPlugin extends RuleSource {
     }
 
     @BinaryTasks
-    void createCoffeeScriptTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, @Path("buildDir") final File buildDir) {
+    void createCoffeeScriptTasks(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary, @Path("buildDir") final File buildDir) {
         tasks.beforeEach(PlayCoffeeScriptCompile.class, new Action<PlayCoffeeScriptCompile>() {
             @Override
             public void execute(PlayCoffeeScriptCompile coffeeScriptCompile) {
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayDistributionPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayDistributionPlugin.java
index 1dd7187..3dbdf62 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayDistributionPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayDistributionPlugin.java
@@ -25,22 +25,16 @@ import org.gradle.api.internal.file.copy.CopySpecInternal;
 import org.gradle.api.tasks.Copy;
 import org.gradle.api.tasks.application.CreateStartScripts;
 import org.gradle.api.tasks.bundling.Zip;
-import org.gradle.internal.Actions;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.jvm.tasks.Jar;
-import org.gradle.model.Defaults;
-import org.gradle.model.Model;
-import org.gradle.model.Mutate;
-import org.gradle.model.Path;
-import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
+import org.gradle.model.*;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.play.PlayApplicationBinarySpec;
-import org.gradle.play.distribution.PlayDistributionContainer;
 import org.gradle.play.distribution.PlayDistribution;
-import org.gradle.play.internal.distribution.DefaultPlayDistributionContainer;
+import org.gradle.play.distribution.PlayDistributionContainer;
 import org.gradle.play.internal.distribution.DefaultPlayDistribution;
+import org.gradle.play.internal.distribution.DefaultPlayDistributionContainer;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
@@ -62,7 +56,7 @@ public class PlayDistributionPlugin extends RuleSource {
     }
 
     @Mutate
-    void createLifecycleTasks(CollectionBuilder<Task> tasks) {
+    void createLifecycleTasks(ModelMap<Task> tasks) {
         tasks.create("dist", new Action<Task>() {
             @Override
             public void execute(Task task) {
@@ -85,14 +79,14 @@ public class PlayDistributionPlugin extends RuleSource {
         FileOperations fileOperations = serviceRegistry.get(FileOperations.class);
         Instantiator instantiator = serviceRegistry.get(Instantiator.class);
         for (PlayApplicationBinarySpec binary : binaryContainer.withType(PlayApplicationBinarySpec.class)) {
-            PlayDistribution distribution = instantiator.newInstance(DefaultPlayDistribution.class, binary.getName(), fileOperations.copySpec(Actions.doNothing()), binary);
+            PlayDistribution distribution = instantiator.newInstance(DefaultPlayDistribution.class, binary.getName(), fileOperations.copySpec(), binary);
             distribution.setBaseName(binary.getName());
             distributions.add(distribution);
         }
     }
 
     @Mutate
-    void createDistributionContentTasks(CollectionBuilder<Task> tasks, final @Path("buildDir") File buildDir,
+    void createDistributionContentTasks(ModelMap<Task> tasks, final @Path("buildDir") File buildDir,
                                         final @Path("distributions") PlayDistributionContainer distributions,
                                         final PlayPluginConfigurations configurations) {
         for (PlayDistribution distribution : distributions.withType(PlayDistribution.class)) {
@@ -149,7 +143,7 @@ public class PlayDistributionPlugin extends RuleSource {
     }
 
     @Mutate
-    void createDistributionZipTasks(CollectionBuilder<Task> tasks, final @Path("buildDir") File buildDir, PlayDistributionContainer distributions) {
+    void createDistributionZipTasks(ModelMap<Task> tasks, final @Path("buildDir") File buildDir, PlayDistributionContainer distributions) {
         for (final PlayDistribution distribution : distributions.withType(PlayDistribution.class)) {
             final String stageTaskName = String.format("stage%sDist", StringUtils.capitalize(distribution.getName()));
             final File stageDir = new File(buildDir, "stage");
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayJavaScriptPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayJavaScriptPlugin.java
index 59f0b01..bf16cc4 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayJavaScriptPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayJavaScriptPlugin.java
@@ -21,12 +21,13 @@ import org.gradle.api.Incubating;
 import org.gradle.api.Task;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.internal.LanguageSourceSetInternal;
+import org.gradle.language.base.internal.compile.Compiler;
 import org.gradle.language.javascript.JavaScriptSourceSet;
 import org.gradle.language.javascript.internal.DefaultJavaScriptSourceSet;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.Path;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.platform.base.BinaryTasks;
 import org.gradle.platform.base.LanguageType;
 import org.gradle.platform.base.LanguageTypeBuilder;
@@ -37,7 +38,6 @@ import org.gradle.play.PlayApplicationBinarySpec;
 import org.gradle.play.PlayApplicationSpec;
 import org.gradle.play.internal.javascript.JavaScriptCompileSpec;
 import org.gradle.play.tasks.JavaScriptMinify;
-import org.gradle.language.base.internal.compile.Compiler;
 
 import java.io.File;
 
@@ -56,7 +56,7 @@ public class PlayJavaScriptPlugin extends RuleSource {
     }
 
     @Mutate
-    void createJavascriptSourceSets(CollectionBuilder<PlayApplicationSpec> components) {
+    void createJavascriptSourceSets(ModelMap<PlayApplicationSpec> components) {
         components.beforeEach(new Action<PlayApplicationSpec>() {
             @Override
             public void execute(PlayApplicationSpec playComponent) {
@@ -69,7 +69,7 @@ public class PlayJavaScriptPlugin extends RuleSource {
     }
 
     @BinaryTasks
-    void createJavaScriptTasks(CollectionBuilder<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
+    void createJavaScriptTasks(ModelMap<Task> tasks, final PlayApplicationBinarySpec binary, ServiceRegistry serviceRegistry, @Path("buildDir") final File buildDir) {
         ToolResolver toolResolver = serviceRegistry.get(ToolResolver.class);
         ResolvedTool<Compiler<JavaScriptCompileSpec>> compilerTool = toolResolver.resolveCompiler(JavaScriptCompileSpec.class, binary.getTargetPlatform());
         for (JavaScriptSourceSet javaScriptSourceSet : binary.getSource().withType(JavaScriptSourceSet.class)) {
@@ -83,7 +83,8 @@ public class PlayJavaScriptPlugin extends RuleSource {
         }
     }
 
-    void createJavaScriptMinifyTask(CollectionBuilder<Task> tasks, final JavaScriptSourceSet javaScriptSourceSet, final PlayApplicationBinarySpec binary, final ResolvedTool<Compiler<JavaScriptCompileSpec>> compilerTool,  @Path("buildDir") final File buildDir) {
+    void createJavaScriptMinifyTask(ModelMap
+                                        <Task> tasks, final JavaScriptSourceSet javaScriptSourceSet, final PlayApplicationBinarySpec binary, final ResolvedTool<Compiler<JavaScriptCompileSpec>> compilerTool, @Path("buildDir") final File buildDir) {
         final String minifyTaskName = "minify" + capitalize(binary.getName()) + capitalize(javaScriptSourceSet.getName());
         final File minifyOutputDirectory = new File(buildDir, String.format("%s/src/%s", binary.getName(), minifyTaskName));
         tasks.create(minifyTaskName, JavaScriptMinify.class, new Action<JavaScriptMinify>() {
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayPlugin.java
index de742da..ebdbfb2 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayPlugin.java
@@ -21,7 +21,7 @@ import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 
 /**
- * Plugin for Play Framework component support. Registers the {@link org.gradle.play.PlayApplicationSpec} component type for the {@link org.gradle.platform.base.ComponentSpecContainer}.
+ * Plugin for Play Framework component support. Registers the {@link org.gradle.play.PlayApplicationSpec} component type for the components container.
  */
 @Incubating
 public class PlayPlugin implements Plugin<Project> {
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayTestPlugin.java b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayTestPlugin.java
index c4897b4..fbca0a6 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayTestPlugin.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/plugins/PlayTestPlugin.java
@@ -26,12 +26,13 @@ import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.internal.project.ProjectIdentifier;
 import org.gradle.api.tasks.scala.IncrementalCompileOptions;
 import org.gradle.api.tasks.testing.Test;
+import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.language.base.plugins.LifecycleBasePlugin;
 import org.gradle.language.scala.tasks.PlatformScalaCompile;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.Path;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.play.PlayApplicationBinarySpec;
 import org.gradle.play.internal.PlayApplicationBinarySpecInternal;
@@ -46,7 +47,7 @@ import java.util.Arrays;
 @Incubating
 public class PlayTestPlugin extends RuleSource {
     @Mutate
-    void createTestTasks(CollectionBuilder<Task> tasks, BinaryContainer binaryContainer, final PlayPluginConfigurations configurations,
+    void createTestTasks(ModelMap<Task> tasks, BinaryContainer binaryContainer, final PlayPluginConfigurations configurations,
                          final FileResolver fileResolver, final ProjectIdentifier projectIdentifier, @Path("buildDir") final File buildDir) {
         for (final PlayApplicationBinarySpecInternal binary : binaryContainer.withType(PlayApplicationBinarySpecInternal.class)) {
             final FileCollection testCompileClasspath = getTestCompileClasspath(binary, configurations);
@@ -54,6 +55,7 @@ public class PlayTestPlugin extends RuleSource {
             final String testCompileTaskName = String.format("compile%sTests", StringUtils.capitalize(binary.getName()));
             // TODO:DAZ Model a test suite
             final File testSourceDir = fileResolver.resolve("test");
+            final FileCollection testSources = new SimpleFileCollection(testSourceDir).getAsFileTree().matching(new PatternSet().include("**/*.scala", "**/*.java"));
             final File testClassesDir = new File(buildDir, String.format("%s/testClasses", binary.getName()));
             tasks.create(testCompileTaskName, PlatformScalaCompile.class, new Action<PlatformScalaCompile>() {
                 public void execute(PlatformScalaCompile scalaCompile) {
@@ -62,7 +64,7 @@ public class PlayTestPlugin extends RuleSource {
                     scalaCompile.dependsOn(binary.getBuildTask());
                     scalaCompile.setPlatform(binary.getTargetPlatform().getScalaPlatform());
                     scalaCompile.setDestinationDir(testClassesDir);
-                    scalaCompile.setSource(testSourceDir);
+                    scalaCompile.setSource(testSources);
                     String targetCompatibility = binary.getTargetPlatform().getJavaPlatform().getTargetCompatibility().getMajorVersion();
                     scalaCompile.setSourceCompatibility(targetCompatibility);
                     scalaCompile.setTargetCompatibility(targetCompatibility);
@@ -102,7 +104,7 @@ public class PlayTestPlugin extends RuleSource {
     }
 
     @Mutate
-    void attachTestSuitesToCheckTask(CollectionBuilder<Task> tasks, final BinaryContainer binaries) {
+    void attachTestSuitesToCheckTask(ModelMap<Task> tasks, final BinaryContainer binaries) {
         // TODO - binaries aren't an input to this rule, they're an input to the action
         tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME, new Action<Task>() {
             @Override
@@ -114,4 +116,4 @@ public class PlayTestPlugin extends RuleSource {
             }
         });
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/platform-play/src/main/java/org/gradle/play/tasks/PlayRun.java b/subprojects/platform-play/src/main/java/org/gradle/play/tasks/PlayRun.java
index 8314eeb..b21d327 100644
--- a/subprojects/platform-play/src/main/java/org/gradle/play/tasks/PlayRun.java
+++ b/subprojects/platform-play/src/main/java/org/gradle/play/tasks/PlayRun.java
@@ -58,7 +58,6 @@ public class PlayRun extends ConventionTask {
 
     private BaseForkOptions forkOptions;
 
-    private PlayApplicationRunnerToken runnerToken;
     private ResolvedTool<PlayApplicationRunner> playApplicationRunnerTool;
 
     /**
@@ -83,7 +82,7 @@ public class PlayRun extends ConventionTask {
         PlayRunSpec spec = new DefaultPlayRunSpec(applicationJars, getProject().getProjectDir(), getForkOptions(), httpPort);
 
         try {
-            runnerToken = playApplicationRunnerTool.get().start(spec);
+            PlayApplicationRunnerToken runnerToken = playApplicationRunnerTool.get().start(spec);
             progressLogger.completed();
             progressLogger = progressLoggerFactory.newOperation(PlayRun.class)
                     .start(String.format("Run Play App at http://localhost:%d/", httpPort),
diff --git a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginTest.groovy b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginTest.groovy
index cbf34d3..cc39c09 100644
--- a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginTest.groovy
+++ b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayCoffeeScriptPluginTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.api.Action
 import org.gradle.api.file.SourceDirectorySet
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.coffeescript.CoffeeScriptSourceSet
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.internal.ComponentSpecInternal
 import org.gradle.play.PlayApplicationSpec
 import spock.lang.Specification
@@ -28,7 +28,7 @@ import spock.lang.Specification
 class PlayCoffeeScriptPluginTest extends Specification {
     def "adds coffeescript source sets to play components" () {
         def plugin = new PlayCoffeeScriptPlugin()
-        def components = Mock(CollectionBuilder)
+        def components = Mock(ModelMap)
         def sources = Mock(FunctionalSourceSet)
         def sourceSet = Mock(CoffeeScriptSourceSet)
         def sourceDirSet = Mock(SourceDirectorySet)
diff --git a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayDistributionPluginTest.groovy b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayDistributionPluginTest.groovy
index 7f1c3ff..b0d6477 100644
--- a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayDistributionPluginTest.groovy
+++ b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayDistributionPluginTest.groovy
@@ -36,7 +36,7 @@ import org.gradle.api.tasks.bundling.Zip
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.jvm.tasks.Jar
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.BinaryContainer
 import org.gradle.platform.base.BinaryTasksCollection
 import org.gradle.play.PlayApplicationBinarySpec
@@ -94,7 +94,7 @@ class PlayDistributionPluginTest extends Specification {
         binary.getJarFile() >> Stub(File) {
             getName() >> "playBinary.zip"
         }
-        CollectionBuilder tasks = Mock(CollectionBuilder) {
+        ModelMap tasks = Mock(ModelMap) {
             get("createPlayBinaryStartScripts") >> Stub(CreateStartScripts)
             get("createPlayBinaryDistributionJar") >> Stub(Jar)
         }
@@ -163,7 +163,7 @@ class PlayDistributionPluginTest extends Specification {
         File buildDir = new File("")
         DomainObjectSet jarTasks = Stub(DomainObjectSet)
         PlayApplicationBinarySpec binary = binary("playBinary", jarTasks)
-        CollectionBuilder tasks = Mock(CollectionBuilder) {
+        ModelMap tasks = Mock(ModelMap) {
             get("stagePlayBinaryDist") >> Stub(Copy)
         }
         PlayDistribution distribution = Mock(PlayDistribution) {
diff --git a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayJavaScriptPluginTest.groovy b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayJavaScriptPluginTest.groovy
index 1354ac4..63c905b 100644
--- a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayJavaScriptPluginTest.groovy
+++ b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayJavaScriptPluginTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.api.Action
 import org.gradle.api.file.SourceDirectorySet
 import org.gradle.language.base.FunctionalSourceSet
 import org.gradle.language.javascript.JavaScriptSourceSet
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.internal.ComponentSpecInternal
 import org.gradle.play.PlayApplicationSpec
 import spock.lang.Specification
@@ -28,7 +28,7 @@ import spock.lang.Specification
 class PlayJavaScriptPluginTest extends Specification {
     def "adds javaScript source sets to play components" () {
         def plugin = new PlayJavaScriptPlugin()
-        def components = Mock(CollectionBuilder)
+        def components = Mock(ModelMap)
         def sources = Mock(FunctionalSourceSet)
         def sourceSet = Mock(JavaScriptSourceSet)
         def sourceDirSet = Mock(SourceDirectorySet)
diff --git a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayTestPluginTest.groovy b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayTestPluginTest.groovy
index 284ebc1..b924bf6 100644
--- a/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayTestPluginTest.groovy
+++ b/subprojects/platform-play/src/test/groovy/org/gradle/play/plugins/PlayTestPluginTest.groovy
@@ -24,7 +24,7 @@ import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.project.ProjectIdentifier
 import org.gradle.api.tasks.testing.Test
 import org.gradle.language.scala.tasks.PlatformScalaCompile
-import org.gradle.model.collection.CollectionBuilder
+import org.gradle.model.ModelMap
 import org.gradle.platform.base.BinaryContainer
 import org.gradle.platform.base.BinaryTasksCollection
 import org.gradle.play.internal.PlayApplicationBinarySpecInternal
@@ -35,7 +35,7 @@ import spock.lang.Specification
 
 class PlayTestPluginTest extends Specification {
 
-    CollectionBuilder<Task> taskCollectionBuilder = Mock(CollectionBuilder)
+    ModelMap<Task> taskModelMap = Mock(ModelMap)
     def binaryContainer = Mock(BinaryContainer)
     def projectIdentifier = Mock(ProjectIdentifier)
     def binary = Mock(PlayApplicationBinarySpecInternal)
@@ -67,12 +67,12 @@ class PlayTestPluginTest extends Specification {
         1 * fileResolver.resolve('test') >> new File('test')
 
         when:
-        plugin.createTestTasks(taskCollectionBuilder, binaryContainer, new PlayPluginConfigurations(configurations, dependencyHandler), fileResolver, projectIdentifier, buildDir)
+        plugin.createTestTasks(taskModelMap, binaryContainer, new PlayPluginConfigurations(configurations, dependencyHandler), fileResolver, projectIdentifier, buildDir)
 
         then:
-        1 * taskCollectionBuilder.create("compileSomeBinaryTests", PlatformScalaCompile, _)
-        1 * taskCollectionBuilder.create("testSomeBinary", Test, _)
-        0 * taskCollectionBuilder.create(_)
-        0 * taskCollectionBuilder.create(_, _, _)
+        1 * taskModelMap.create("compileSomeBinaryTests", PlatformScalaCompile, _)
+        1 * taskModelMap.create("testSomeBinary", Test, _)
+        0 * taskModelMap.create(_)
+        0 * taskModelMap.create(_, _, _)
     }
 }
diff --git a/subprojects/platform-play/src/test/groovy/org/gradle/play/tasks/PlayRunTest.groovy b/subprojects/platform-play/src/test/groovy/org/gradle/play/tasks/PlayRunTest.groovy
index 9c7a7a5..7205036 100644
--- a/subprojects/platform-play/src/test/groovy/org/gradle/play/tasks/PlayRunTest.groovy
+++ b/subprojects/platform-play/src/test/groovy/org/gradle/play/tasks/PlayRunTest.groovy
@@ -15,6 +15,7 @@
  */
 
 package org.gradle.play.tasks
+
 import org.gradle.api.internal.file.collections.SimpleFileCollection
 import org.gradle.platform.base.internal.toolchain.ResolvedTool
 import org.gradle.play.internal.run.PlayApplicationRunner
@@ -22,7 +23,8 @@ import org.gradle.play.internal.run.PlayApplicationRunnerToken
 import org.gradle.play.internal.run.PlayRunSpec
 import org.gradle.util.RedirectStdIn
 import org.gradle.util.TestUtil
-import org.junit.Rule
+import org.junit.ClassRule
+import spock.lang.Shared
 import spock.lang.Specification
 
 class PlayRunTest extends Specification {
@@ -34,8 +36,8 @@ class PlayRunTest extends Specification {
     }
     InputStream systemInputStream = Mock()
 
-    @Rule
-    RedirectStdIn redirectStdIn;
+    @Shared @ClassRule
+    RedirectStdIn redirectStdIn
 
     PlayRun playRun
 
@@ -83,4 +85,4 @@ class PlayRunTest extends Specification {
         then:
         1 * playApplicationRunner.start(_) >> runnerToken
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/DeployedPortalIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/DeployedPortalIntegrationSpec.groovy
index 83f9c8d..45564e8 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/DeployedPortalIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/DeployedPortalIntegrationSpec.groovy
@@ -17,11 +17,13 @@
 package org.gradle.plugin.use
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 //These tests depend on https://plugins.gradle.org
 @Requires(TestPrecondition.ONLINE)
+ at LeaksFileHandles
 class DeployedPortalIntegrationSpec extends AbstractIntegrationSpec {
 
     private final static String HELLO_WORLD_PLUGIN_ID = "org.gradle.hello-world"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonCorePluginAlreadyOnClasspathDetectionIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonCorePluginAlreadyOnClasspathDetectionIntegrationSpec.groovy
index 4bb8e88..173eb2f 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonCorePluginAlreadyOnClasspathDetectionIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonCorePluginAlreadyOnClasspathDetectionIntegrationSpec.groovy
@@ -18,9 +18,11 @@ package org.gradle.plugin.use
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 import static org.gradle.plugin.use.resolve.internal.NotNonCorePluginOnClasspathCheckPluginResolver.pluginOnClasspathErrorMessage
 
+ at LeaksFileHandles
 class NonCorePluginAlreadyOnClasspathDetectionIntegrationSpec extends AbstractIntegrationSpec {
 
     private testPluginBuildscriptBlock() {
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonDeclarativePluginUseIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonDeclarativePluginUseIntegrationSpec.groovy
index 48dc0cd..eedcd4b 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonDeclarativePluginUseIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/NonDeclarativePluginUseIntegrationSpec.groovy
@@ -20,10 +20,12 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.plugin.use.resolve.service.PluginResolutionServiceTestServer
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.gradle.test.fixtures.server.http.MavenHttpModule
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 import static org.hamcrest.Matchers.startsWith
 
+ at LeaksFileHandles
 class NonDeclarativePluginUseIntegrationSpec extends AbstractIntegrationSpec {
 
     public static final String PLUGIN_ID = "org.myplugin"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/PostPluginResolutionFailuresIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/PostPluginResolutionFailuresIntegrationSpec.groovy
index 7a51ab6..ab5545f 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/PostPluginResolutionFailuresIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/PostPluginResolutionFailuresIntegrationSpec.groovy
@@ -19,10 +19,12 @@ package org.gradle.plugin.use
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.plugin.use.resolve.service.PluginResolutionServiceTestServer
 import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 import static org.hamcrest.Matchers.startsWith
 
+ at LeaksFileHandles
 class PostPluginResolutionFailuresIntegrationSpec extends AbstractIntegrationSpec {
     def pluginBuilder = new PluginBuilder(file("plugin"))
 
@@ -102,4 +104,4 @@ class PostPluginResolutionFailuresIntegrationSpec extends AbstractIntegrationSpe
             task verify // no-op, but gives us a task to execute
         """
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/RuleSourcePluginUseIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/RuleSourcePluginUseIntegrationSpec.groovy
index 155d927..301fdc4 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/RuleSourcePluginUseIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/RuleSourcePluginUseIntegrationSpec.groovy
@@ -19,8 +19,10 @@ package org.gradle.plugin.use
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.plugin.use.resolve.service.PluginResolutionServiceTestServer
 import org.gradle.test.fixtures.plugin.PluginBuilder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
+ at LeaksFileHandles
 class RuleSourcePluginUseIntegrationSpec extends AbstractIntegrationSpec {
 
     public static final String PLUGIN_ID = "org.myplugin"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingCrossVersionIntegrationTest.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingCrossVersionIntegrationTest.groovy
index 45adaa2..539b26b 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingCrossVersionIntegrationTest.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingCrossVersionIntegrationTest.groovy
@@ -20,9 +20,11 @@ import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.gradle.test.fixtures.server.http.MavenHttpModule
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 @TargetVersions(["2.1+"])
+ at LeaksFileHandles
 class PluginResolutionCachingCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
 
     public static final String PLUGIN_ID = "org.my.myplugin"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingIntegrationTest.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingIntegrationTest.groovy
index ac89d0a..6db0204 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingIntegrationTest.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionCachingIntegrationTest.groovy
@@ -19,10 +19,12 @@ package org.gradle.plugin.use.resolve.service
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.gradle.test.fixtures.server.http.MavenHttpModule
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 import static org.hamcrest.Matchers.startsWith
 
+ at LeaksFileHandles
 class PluginResolutionCachingIntegrationTest extends AbstractIntegrationSpec {
 
     public static final String PLUGIN_ID = "org.my.myplugin"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionDeprecatedClientIntegrationTest.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionDeprecatedClientIntegrationTest.groovy
index 0417e15..a494da9 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionDeprecatedClientIntegrationTest.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionDeprecatedClientIntegrationTest.groovy
@@ -23,10 +23,12 @@ import org.gradle.plugin.use.internal.PluginUsePluginServiceRegistry
 import org.gradle.plugin.use.resolve.service.internal.PersistentCachingPluginResolutionServiceClient
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.gradle.test.fixtures.server.http.MavenHttpModule
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 import static org.gradle.util.Matchers.containsText
 
+ at LeaksFileHandles
 class PluginResolutionDeprecatedClientIntegrationTest extends AbstractIntegrationSpec {
 
     public static final String PLUGIN_ID_1 = "org.my.myplugin_1"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceCommsIntegrationTest.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceCommsIntegrationTest.groovy
index ccdfb4f..680d544 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceCommsIntegrationTest.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceCommsIntegrationTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.plugin.use.resolve.service.internal.ErrorResponse
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.hamcrest.Matchers
 import org.junit.Rule
 import spock.lang.Unroll
@@ -31,6 +32,7 @@ import static org.gradle.util.Matchers.containsText
 /**
  * Tests the communication aspects of working with the plugin resolution service.
  */
+ at LeaksFileHandles
 public class PluginResolutionServiceCommsIntegrationTest extends AbstractIntegrationSpec {
     public static final String PLUGIN_ID = "org.my.myplugin"
     public static final String PLUGIN_VERSION = "1.0"
diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceIntegrationSpec.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceIntegrationSpec.groovy
index cc9cbff..9bc4598 100644
--- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceIntegrationSpec.groovy
+++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceIntegrationSpec.groovy
@@ -17,6 +17,7 @@
 package org.gradle.plugin.use.resolve.service
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.test.fixtures.plugin.PluginBuilder
 import org.hamcrest.Matchers
 import org.junit.Rule
@@ -33,6 +34,7 @@ class PluginResolutionServiceIntegrationSpec extends AbstractIntegrationSpec {
         portal.start()
     }
 
+    @LeaksFileHandles
     def "plugin declared in plugins {} block gets resolved and applied"() {
         portal.expectPluginQuery("org.my.myplugin", "1.0", "my", "plugin", "1.0")
         publishPlugin("org.my.myplugin", "my", "plugin", "1.0")
diff --git a/subprojects/plugin-use/src/main/java/org/gradle/plugin/use/internal/DefaultPluginRequestApplicator.java b/subprojects/plugin-use/src/main/java/org/gradle/plugin/use/internal/DefaultPluginRequestApplicator.java
index 4c4e456..73c1d6f 100644
--- a/subprojects/plugin-use/src/main/java/org/gradle/plugin/use/internal/DefaultPluginRequestApplicator.java
+++ b/subprojects/plugin-use/src/main/java/org/gradle/plugin/use/internal/DefaultPluginRequestApplicator.java
@@ -22,29 +22,25 @@ import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.Nullable;
 import org.gradle.api.Transformer;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.initialization.ClassLoaderScope;
+import org.gradle.api.internal.initialization.ScriptHandlerInternal;
 import org.gradle.api.internal.plugins.*;
 import org.gradle.api.plugins.InvalidPluginException;
 import org.gradle.api.plugins.UnknownPluginException;
 import org.gradle.api.specs.Spec;
 import org.gradle.internal.classpath.ClassPath;
-import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.exceptions.LocationAwareException;
 import org.gradle.plugin.internal.PluginId;
 import org.gradle.plugin.use.resolve.internal.*;
 
-import java.io.File;
 import java.util.*;
 
 import static org.gradle.util.CollectionUtils.any;
 import static org.gradle.util.CollectionUtils.collect;
 
 public class DefaultPluginRequestApplicator implements PluginRequestApplicator {
-
     private final PluginRegistry pluginRegistry;
     private final PluginResolver pluginResolver;
 
@@ -53,7 +49,7 @@ public class DefaultPluginRequestApplicator implements PluginRequestApplicator {
         this.pluginResolver = pluginResolver;
     }
 
-    public void applyPlugins(PluginRequests requests, final ScriptHandler scriptHandler, @Nullable final PluginManagerInternal target, ClassLoaderScope classLoaderScope) {
+    public void applyPlugins(PluginRequests requests, final ScriptHandlerInternal scriptHandler, @Nullable final PluginManagerInternal target, ClassLoaderScope classLoaderScope) {
         if (requests.isEmpty()) {
             defineScriptHandlerClassScope(scriptHandler, classLoaderScope);
             return;
@@ -88,7 +84,7 @@ public class DefaultPluginRequestApplicator implements PluginRequestApplicator {
                             public void addLegacy(PluginId pluginId, final String m2RepoUrl, Object dependencyNotation) {
                                 legacyActualPluginIds.put(result, pluginId);
                                 repoUrls.add(m2RepoUrl);
-                                scriptHandler.getDependencies().add(ScriptHandler.CLASSPATH_CONFIGURATION, dependencyNotation);
+                                scriptHandler.addScriptClassPathDependency(dependencyNotation);
                             }
 
                             @Override
@@ -141,10 +137,8 @@ public class DefaultPluginRequestApplicator implements PluginRequestApplicator {
         }
     }
 
-    private void defineScriptHandlerClassScope(ScriptHandler scriptHandler, ClassLoaderScope classLoaderScope) {
-        Configuration classpathConfiguration = scriptHandler.getConfigurations().getByName(ScriptHandler.CLASSPATH_CONFIGURATION);
-        Set<File> files = classpathConfiguration.getFiles();
-        ClassPath classPath = new DefaultClassPath(files);
+    private void defineScriptHandlerClassScope(ScriptHandlerInternal scriptHandler, ClassLoaderScope classLoaderScope) {
+        ClassPath classPath = scriptHandler.getScriptClassPath();
         classLoaderScope.export(classPath);
         classLoaderScope.lock();
     }
diff --git a/subprojects/plugin-use/src/testFixtures/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceTestServer.groovy b/subprojects/plugin-use/src/testFixtures/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceTestServer.groovy
index edde6a4..cabfba0 100644
--- a/subprojects/plugin-use/src/testFixtures/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceTestServer.groovy
+++ b/subprojects/plugin-use/src/testFixtures/groovy/org/gradle/plugin/use/resolve/service/PluginResolutionServiceTestServer.groovy
@@ -26,6 +26,7 @@ import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.MavenHttpRepository
 import org.gradle.util.ConfigureUtil
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.rules.ExternalResource
 
 import javax.servlet.http.HttpServletRequest
@@ -33,6 +34,7 @@ import javax.servlet.http.HttpServletResponse
 
 import static org.gradle.test.fixtures.server.http.HttpServer.Utils.json
 
+ at LeaksFileHandles
 class PluginResolutionServiceTestServer extends ExternalResource {
 
     public final static String API_PATH = "api"
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
index cd363af..f587e4e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
@@ -15,12 +15,14 @@
  */
 package org.gradle.api.plugins
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 class BasePluginIntegrationTest extends AbstractIntegrationSpec {
 
     @Requires(TestPrecondition.MANDATORY_FILE_LOCKING)
+    @LeaksFileHandles
     def "clean failure message indicates file"() {
         given:
         executer.requireGradleHome()
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 c50d179..20196bf 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
@@ -17,11 +17,13 @@
 package org.gradle.api.plugins
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Issue
 
 class BuildSrcPluginTest extends AbstractIntegrationSpec {
 
     @Issue("GRADLE-2001") // when using the daemon
+    @LeaksFileHandles
     def "can use plugin from buildSrc that changes"() {
         given:
         executer.requireIsolatedDaemons() // make sure we get the same daemon both times
@@ -29,14 +31,14 @@ class BuildSrcPluginTest extends AbstractIntegrationSpec {
         buildFile << "apply plugin: 'test-plugin'"
 
         file("buildSrc/settings.gradle") << "include 'testplugin'"
-        
+
         file("buildSrc/build.gradle") << """
             apply plugin: "groovy"
             dependencies {
                 runtime project(":testplugin")
             }
         """
-                
+
         file("buildSrc/testplugin/build.gradle") << """
             apply plugin: "groovy"
 
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 0754848..bf47af0 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
@@ -22,6 +22,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
@@ -58,10 +59,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "compileWithAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "", // no Java
-                "$annotationText class Groovy {}"
+            "", // no Java
+            "$annotationText class Groovy {}"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         succeeds("compileGroovy")
@@ -74,10 +76,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "compileBadCodeWithAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "", // no Java
-                "$annotationText class Groovy { def m() { $nonCompilableImperativeGroovy } }"
+            "", // no Java
+            "$annotationText class Groovy { def m() { $nonCompilableImperativeGroovy } }"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         fails("compileGroovy")
@@ -93,10 +96,10 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "compileBadCodeWithoutAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                false,
-                "", // no Java
-                "$annotationText class Groovy { def m() { $nonCompilableImperativeGroovy } }"
+            "", // no Java
+            "$annotationText class Groovy { def m() { $nonCompilableImperativeGroovy } }"
         )
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         fails("compileGroovy")
@@ -114,10 +117,10 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "compileBadCodeWithAnnotationProcessorDisabled"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "", // no Java
-                "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }"
-        )
+            "", // no Java
+            "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }")
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         buildFile << """
             compileGroovy {
@@ -141,10 +144,10 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "jointCompileBadCodeWithoutAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                false,
-                "public class Java {}",
-                "class Groovy { def m() { $nonCompilableImperativeGroovy } }"
+            "public class Java {}",
+            "class Groovy { def m() { $nonCompilableImperativeGroovy } }"
         )
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         fails("compileGroovy")
@@ -162,10 +165,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "jointCompileWithAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "$annotationText public class Java {}",
-                "$annotationText class Groovy {}"
+            "$annotationText public class Java {}",
+            "$annotationText class Groovy {}"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         succeeds("compileGroovy")
@@ -178,13 +182,33 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
         file('build/classes/main/Java$$Generated.class').exists()
     }
 
+    def "jointCompileWithJavaAnnotationProcessorOnly"() {
+        when:
+        writeAnnotationProcessingBuild(
+            "$annotationText public class Java {}",
+            "$annotationText class Groovy {}"
+        )
+        setupAnnotationProcessor()
+
+        then:
+        succeeds("compileGroovy")
+        !errorOutput
+        file('build/classes/main/Java.class').exists()
+        file('build/classes/main/Groovy.class').exists()
+        !file('build/classes/main/Groovy$$Generated.java').exists()
+        file('build/classes/main/Java$$Generated.java').exists()
+        !file('build/classes/main/Groovy$$Generated.class').exists()
+        file('build/classes/main/Java$$Generated.class').exists()
+    }
+
     def "jointCompileBadCodeWithAnnotationProcessor"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "$annotationText public class Java {}",
-                "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }"
+            "$annotationText public class Java {}",
+            "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         then:
         fails("compileGroovy")
@@ -207,10 +231,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "jointCompileWithAnnotationProcessorDisabled"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "$annotationText public class Java {}",
-                "$annotationText class Groovy { }"
+            "$annotationText public class Java {}",
+            "$annotationText class Groovy { }"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         buildFile << """
             compileGroovy {
@@ -232,10 +257,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     def "jointCompileBadCodeWithAnnotationProcessorDisabled"() {
         when:
         writeAnnotationProcessingBuild(
-                true,
-                "$annotationText public class Java {}",
-                "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }"
+            "$annotationText public class Java {}",
+            "$annotationText class Groovy { void m() { $nonCompilableImperativeGroovy } }"
         )
+        setupAnnotationProcessor()
+        enableAnnotationProcessingOfJavaStubs()
 
         buildFile << """
             compileGroovy {
@@ -522,7 +548,7 @@ ${compilerConfiguration()}
         }
     }
 
-    def writeAnnotationProcessingBuild(boolean useProcessor, String java, String groovy) {
+    def writeAnnotationProcessingBuild(String java, String groovy) {
         buildFile << """
             apply plugin: "groovy"
             repositories { mavenCentral() }
@@ -534,16 +560,6 @@ ${compilerConfiguration()}
             }
         """
 
-        if (useProcessor) {
-            settingsFile << "include 'processor'"
-            writeAnnotationProcessorProject()
-            buildFile << """
-                dependencies {
-                    compile project(":processor")
-                }
-            """
-        }
-
         if (java) {
             file("src/main/groovy/Java.java") << java
         }
@@ -551,4 +567,20 @@ ${compilerConfiguration()}
             file("src/main/groovy/Groovy.groovy") << groovy
         }
     }
+
+    private void setupAnnotationProcessor() {
+        settingsFile << "include 'processor'"
+        writeAnnotationProcessorProject()
+        buildFile << """
+                dependencies {
+                    compile project(":processor")
+                }
+            """
+    }
+
+    private TestFile enableAnnotationProcessingOfJavaStubs() {
+        buildFile << """
+                compileGroovy.groovyOptions.javaAnnotationProcessing = true
+            """
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
index 03abe08..a891597 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
@@ -15,6 +15,9 @@
  */
 package org.gradle.groovy.compile
 
+import org.gradle.test.fixtures.file.LeaksFileHandles
+
+ at LeaksFileHandles
 class InProcessGroovyCompilerIntegrationTest extends ApiGroovyCompilerIntegrationSpec {
 
     String compilerConfiguration() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/ComponentReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/ComponentReportIntegrationTest.groovy
index d2ba808..2eb8749 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/ComponentReportIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/ComponentReportIntegrationTest.groovy
@@ -40,13 +40,13 @@ No components defined for this project.
 Additional source sets
 ----------------------
 Java source 'main:java'
-    src/main/java
-JVM resources 'main:resources'
-    src/main/resources
+    srcDir: src/main/java
 Java source 'test:java'
-    src/test/java
+    srcDir: src/test/java
+JVM resources 'main:resources'
+    srcDir: src/main/resources
 JVM resources 'test:resources'
-    src/test/resources
+    srcDir: src/test/resources
 
 Additional binaries
 -------------------
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
index 0614a46..521f8b7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
@@ -20,6 +20,7 @@ package org.gradle.java.compile
 import org.gradle.api.Action
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.file.ClassFile
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
@@ -86,6 +87,7 @@ abstract class BasicJavaCompilerIntegrationSpec extends AbstractIntegrationSpec
         file('encoded.out').getText("utf-8") == "\u03b1\u03b2\u03b3"
     }
 
+    @LeaksFileHandles
     def compilesWithSpecifiedDebugSettings() {
         given:
         goodCode()
@@ -148,7 +150,6 @@ public class FxApp extends Application {
     def buildScript() {
         '''
 apply plugin: "java"
-
 repositories {
     mavenCentral()
 }
@@ -233,6 +234,7 @@ class Main {
         return new ClassFile(file(path))
     }
 
+    @LeaksFileHandles
     def "can use annotation processor"() {
         when:
         buildFile << """
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
index 715bb93..d95de55 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.java.compile
 
+import org.gradle.test.fixtures.file.LeaksFileHandles
+
 abstract class JavaCompilerIntegrationSpec extends BasicJavaCompilerIntegrationSpec {
     def setup() {
         buildFile << """
@@ -24,6 +26,7 @@ abstract class JavaCompilerIntegrationSpec extends BasicJavaCompilerIntegrationS
 """
     }
 
+    @LeaksFileHandles
     def compileWithLongClasspath() {
         given:
         goodCode()
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy
index 4bcec9f..5684fb4 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestTaskIntegrationTest.groovy
@@ -22,12 +22,14 @@ import spock.lang.Issue;
 public class TestTaskIntegrationTest extends AbstractIntegrationSpec {
 
     @Issue("GRADLE-2702")
-    def "should not resolve configurations when there are no tests"() {
+    def "should not resolve configuration results when there are no tests"() {
         buildFile << """
             apply plugin: 'java'
 
-            configure([configurations.testRuntime, configurations.testCompile]) {
-                incoming.beforeResolve { assert false : "should not be resolved" }
+            configure([configurations.testRuntime, configurations.testCompile]) { configuration ->
+                incoming.afterResolve {
+                    assert configuration.resolvedState == org.gradle.api.internal.artifacts.configurations.ConfigurationInternal.InternalState.TASK_DEPENDENCIES_RESOLVED : "should not be resolved"
+                }
             }
         """
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitConsoleLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitConsoleLoggingIntegrationTest.groovy
index ffeacb7..c90cfa7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitConsoleLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitConsoleLoggingIntegrationTest.groovy
@@ -19,25 +19,21 @@ 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.gradle.integtests.fixtures.executer.ExecutionResult
-import org.gradle.util.TextUtil
 import org.junit.Rule
-import org.junit.Test
 
 import static org.hamcrest.Matchers.equalTo
 
 // cannot make assumptions about order in which test methods of JUnit4Test get executed
 class JUnitConsoleLoggingIntegrationTest extends AbstractIntegrationSpec {
     @Rule TestResources resources = new TestResources(temporaryFolder)
-    ExecutionResult result
 
     def setup() {
-        executer.noExtraLogging().withStackTraceChecksDisabled().withTasks("test")
+        executer.noExtraLogging().withStackTraceChecksDisabled()
     }
 
     def "defaultLifecycleLogging"() {
         when:
-        result = executer.runWithFailure()
+        fails("test")
 
         then:
         outputContains("""
@@ -48,7 +44,8 @@ org.gradle.JUnit4Test > badTest FAILED
 
     def "customQuietLogging"() {
         when:
-        result = executer.withArguments("-q").runWithFailure()
+        args("-q")
+        fails("test")
 
         then:
         outputContains("""
@@ -65,7 +62,8 @@ badTest FAILED
 
     def "standardOutputLogging"() {
         when:
-        result = executer.withArguments("-q").runWithFailure()
+        args("-q")
+        fails("test")
 
         then:
         outputContains("""
@@ -100,7 +98,7 @@ public class EncodingTest {
 }
 """
         when:
-        executer.withTasks("test").runWithFailure()
+        fails("test")
 
         then:
         new DefaultTestExecutionResult(testDirectory)
@@ -114,8 +112,4 @@ xml entity: &
                 .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
     }
 
-
-    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/TestNGConsoleLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGConsoleLoggingIntegrationTest.groovy
index a63743b..1d0dcd8 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGConsoleLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGConsoleLoggingIntegrationTest.groovy
@@ -17,14 +17,13 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.util.TextUtil
 
 // can make assumptions about order in which test methods of TestNGTest get executed
 // because the methods are chained with 'methodDependsOn'
 class TestNGConsoleLoggingIntegrationTest extends AbstractIntegrationSpec {
 
     def setup() {
-        executer.noExtraLogging().withStackTraceChecksDisabled().withTasks("test")
+        executer.noExtraLogging().withStackTraceChecksDisabled()
 
         buildFile << """
             apply plugin: "groovy"
@@ -85,7 +84,7 @@ class TestNGConsoleLoggingIntegrationTest extends AbstractIntegrationSpec {
 
     def "defaultLifecycleLogging"() {
         when:
-        result = executer.runWithFailure()
+        fails "test"
 
         then:
         outputContains("""
@@ -96,7 +95,8 @@ Gradle test > org.gradle.TestNGTest.badTest FAILED
 
     def customQuietLogging() {
         when:
-        result = executer.withArguments("-q").runWithFailure()
+        args "-q"
+        fails "test"
 
         then:
         outputContains("""
@@ -153,7 +153,8 @@ Gradle suite FAILED
         """
 
         when:
-        result = executer.withArguments("-q").runWithFailure()
+        args "-q"
+        fails "test"
 
         then:
         outputContains("""
@@ -164,9 +165,4 @@ 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/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java
index b767e46..6801f2a 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,7 +18,6 @@ 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.internal.Actions;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.internal.reflect.Instantiator;
 
@@ -34,6 +33,6 @@ public class DefaultDistributionContainer extends AbstractNamedDomainObjectConta
     }
 
     protected Distribution doCreate(String name) {
-        return getInstantiator().newInstance(DefaultDistribution.class, name, fileOperations.copySpec(Actions.doNothing()));
+        return getInstantiator().newInstance(DefaultDistribution.class, name, fileOperations.copySpec());
     }
 }
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 d287703..8997758 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
@@ -47,6 +47,7 @@ class DistributionPlugin implements Plugin<ProjectInternal> {
     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_ASSEMBLE_NAME = "assembleDist"
     private static final String TASK_INSTALL_NAME = "installDist"
 
     private final Instantiator instantiator
@@ -122,7 +123,10 @@ class DistributionPlugin implements Plugin<ProjectInternal> {
     }
 
     private void addAssembleTask(Project project, Distribution distribution, Task... tasks) {
-        def taskName = "assemble" + distribution.name.capitalize() + "Dist"
+        def taskName = TASK_ASSEMBLE_NAME;
+        if (MAIN_DISTRIBUTION_NAME != distribution.name) {
+            taskName = "assemble" + distribution.name.capitalize() + "Dist"
+        }
         Task assembleTask = project.getTasks().create(taskName);
         assembleTask.setDescription("Assembles the " + distribution.name + " distributions");
         assembleTask.setGroup(DISTRIBUTION_GROUP);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/AbstractLanguageSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/AbstractLanguageSourceSet.java
index 2407e32..bb48acc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/AbstractLanguageSourceSet.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/AbstractLanguageSourceSet.java
@@ -83,4 +83,5 @@ public abstract class AbstractLanguageSourceSet extends AbstractBuildableModelEl
     public SourceDirectorySet getSource() {
         return source;
     }
+
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/ClassDirectoryBinarySpecInternal.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/ClassDirectoryBinarySpecInternal.java
index 5d98367..792ece2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/ClassDirectoryBinarySpecInternal.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/ClassDirectoryBinarySpecInternal.java
@@ -17,9 +17,12 @@
 package org.gradle.api.internal.jvm;
 
 import org.gradle.jvm.ClassDirectoryBinarySpec;
+import org.gradle.language.base.LanguageSourceSet;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
 import org.gradle.platform.base.internal.BinarySpecInternal;
 
 public interface ClassDirectoryBinarySpecInternal extends ClassDirectoryBinarySpec, BinarySpecInternal {
     BinaryNamingScheme getNamingScheme();
+
+    void addSourceSet(LanguageSourceSet sourceSet);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/DefaultClassDirectoryBinarySpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/DefaultClassDirectoryBinarySpec.java
index 01a5cc8..971f46d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/DefaultClassDirectoryBinarySpec.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/jvm/DefaultClassDirectoryBinarySpec.java
@@ -141,10 +141,12 @@ public class DefaultClassDirectoryBinarySpec extends AbstractBuildableModelEleme
         throw new UnsupportedOperationException();
     }
 
+    @Override
     public void sources(Action<? super PolymorphicDomainObjectContainer<LanguageSourceSet>> action) {
         throw new UnsupportedOperationException();
     }
 
+    @Override
     public DomainObjectSet<LanguageSourceSet> getSource() {
         return sourceSets;
     }
@@ -153,6 +155,11 @@ public class DefaultClassDirectoryBinarySpec extends AbstractBuildableModelEleme
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void addSourceSet(LanguageSourceSet sourceSet) {
+        sourceSets.add(sourceSet);
+    }
+
     public String getDisplayName() {
         return namingScheme.getDescription();
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java
index f104c5a..79b22ce 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.tasks.testing;
 
+import org.gradle.api.Nullable;
+
 import java.io.Serializable;
 
 public abstract class AbstractTestDescriptor implements TestDescriptorInternal, Serializable {
@@ -42,4 +44,10 @@ public abstract class AbstractTestDescriptor implements TestDescriptorInternal,
     public TestDescriptorInternal getParent() {
         return null;
     }
+
+    @Nullable
+    @Override
+    public Object getOwnerBuildOperationId() {
+        return null;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
index e3f327f..f878847 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.tasks.testing;
 
+import org.gradle.api.Nullable;
+
 public class DecoratingTestDescriptor implements TestDescriptorInternal {
     private final TestDescriptorInternal descriptor;
     private final TestDescriptorInternal parent;
@@ -30,6 +32,10 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
         return descriptor.toString();
     }
 
+    public TestDescriptorInternal getDescriptor() {
+        return descriptor;
+    }
+
     public TestDescriptorInternal getParent() {
         return parent;
     }
@@ -38,6 +44,12 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
         return descriptor.getId();
     }
 
+    @Nullable
+    @Override
+    public Object getOwnerBuildOperationId() {
+        return descriptor.getOwnerBuildOperationId();
+    }
+
     public String getClassName() {
         return descriptor.getClassName();
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java
index 42be070..175256d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java
@@ -25,4 +25,11 @@ public interface TestDescriptorInternal extends TestDescriptor {
     TestDescriptorInternal getParent();
 
     Object getId();
+
+    /**
+     * Returns the identifier for the build operation (eg test task) that owns this test.
+     * Not null only for a root test suite with no parent test.
+     */
+    @Nullable
+    Object getOwnerBuildOperationId();
 }
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 f662608..c09ba36 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
@@ -28,6 +28,7 @@ import org.gradle.api.internal.tasks.testing.worker.ForkingTestClassProcessor;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.internal.Factory;
 import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.progress.OperationIdGenerator;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
@@ -49,7 +50,7 @@ public class DefaultTestExecuter implements TestExecuter {
         final Factory<TestClassProcessor> forkingProcessorFactory = new Factory<TestClassProcessor>() {
             public TestClassProcessor create() {
                 return new ForkingTestClassProcessor(workerFactory, testInstanceFactory, testTask,
-                        testTask.getClasspath(), testFramework.getWorkerConfigurationAction());
+                    testTask.getClasspath(), testFramework.getWorkerConfigurationAction());
             }
         };
         Factory<TestClassProcessor> reforkingProcessorFactory = new Factory<TestClassProcessor>() {
@@ -59,7 +60,7 @@ public class DefaultTestExecuter implements TestExecuter {
         };
 
         TestClassProcessor processor = new MaxNParallelTestClassProcessor(testTask.getMaxParallelForks(),
-                reforkingProcessorFactory, actorFactor);
+            reforkingProcessorFactory, actorFactor);
 
         final FileTree testClassFiles = testTask.getCandidateClassFiles();
 
@@ -72,6 +73,9 @@ public class DefaultTestExecuter implements TestExecuter {
         } else {
             detector = new DefaultTestClassScanner(testClassFiles, null, processor);
         }
-        new TestMainAction(detector, processor, testResultProcessor, new TrueTimeProvider(), testTask.getPath(), String.format("Gradle Test Run %s", testTask.getPath())).run();
+
+        final Object testTaskOperationId = OperationIdGenerator.generateId(testTask);
+
+        new TestMainAction(detector, processor, testResultProcessor, new TrueTimeProvider(), testTaskOperationId, testTask.getPath(), String.format("Gradle Test Run %s", testTask.getPath())).run();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
index a6b7d5f..6ada359 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing.processors;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
 import org.gradle.internal.TimeProvider;
@@ -25,20 +26,22 @@ public class TestMainAction implements Runnable {
     private final TestClassProcessor processor;
     private final TestResultProcessor resultProcessor;
     private final TimeProvider timeProvider;
-    private final String rootTestSuiteId;
+    private final Object testTaskOperationId;
+    private final Object rootTestSuiteId;
     private final String displayName;
 
-    public TestMainAction(Runnable detector, TestClassProcessor processor, TestResultProcessor resultProcessor, TimeProvider timeProvider, String rootTestSuiteId, String displayName) {
+    public TestMainAction(Runnable detector, TestClassProcessor processor, TestResultProcessor resultProcessor, TimeProvider timeProvider, Object testTaskOperationId, Object rootTestSuiteId, String displayName) {
         this.detector = detector;
         this.processor = processor;
         this.resultProcessor = new AttachParentTestResultProcessor(resultProcessor);
         this.timeProvider = timeProvider;
+        this.testTaskOperationId = testTaskOperationId;
         this.rootTestSuiteId = rootTestSuiteId;
         this.displayName = displayName;
     }
 
     public void run() {
-        RootTestSuiteDescriptor suite = new RootTestSuiteDescriptor(rootTestSuiteId, displayName);
+        RootTestSuiteDescriptor suite = new RootTestSuiteDescriptor(rootTestSuiteId, displayName, testTaskOperationId);
         resultProcessor.started(suite, new TestStartEvent(timeProvider.getCurrentTime()));
         try {
             processor.startProcessing(resultProcessor);
@@ -52,9 +55,18 @@ public class TestMainAction implements Runnable {
         }
     }
 
-    private static class RootTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
-        public RootTestSuiteDescriptor(Object id, String name) {
+    public static final class RootTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
+        private final Object testTaskOperationId;
+
+        private RootTestSuiteDescriptor(Object id, String name, Object testTaskOperationId) {
             super(id, name);
+            this.testTaskOperationId = testTaskOperationId;
+        }
+
+        @Nullable
+        @Override
+        public Object getOwnerBuildOperationId() {
+            return testTaskOperationId;
         }
 
         @Override
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 12a031c..5d68a39 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,6 +15,7 @@
  */
 package org.gradle.api.internal.tasks.testing.results;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
 
 public class UnknownTestDescriptor implements TestDescriptorInternal {
@@ -38,4 +39,10 @@ public class UnknownTestDescriptor implements TestDescriptorInternal {
     public TestDescriptorInternal getParent() {
         return null;
     }
-}
\ No newline at end of file
+
+    @Nullable
+    @Override
+    public Object getOwnerBuildOperationId() {
+        return null;
+    }
+}
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 7796fab..4e36ba2 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
@@ -37,7 +37,7 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask;
 import org.gradle.configuration.project.ProjectConfigurationActionContainer;
 import org.gradle.jvm.tasks.Jar;
 import org.gradle.language.base.plugins.LifecycleBasePlugin;
-import org.gradle.model.internal.core.ActionBackedModelAction;
+import org.gradle.model.internal.core.NoInputsModelAction;
 import org.gradle.model.internal.core.ModelActionRole;
 import org.gradle.model.internal.core.ModelReference;
 import org.gradle.model.internal.core.rule.describe.SimpleModelRuleDescriptor;
@@ -173,7 +173,7 @@ public class BasePlugin implements Plugin<Project> {
 
     private void configureAssemble(final ProjectInternal project) {
         // Note, this is implicitly retaining the project instance which is a problem for reuse
-        project.getModelRegistry().configure(ModelActionRole.Mutate, ActionBackedModelAction.of(ModelReference.of("tasks.assemble", Task.class), new SimpleModelRuleDescriptor("BasePlugin#configureAssemble"), new Action<Task>() {
+        project.getModelRegistry().configure(ModelActionRole.Mutate, NoInputsModelAction.of(ModelReference.of("tasks.assemble", Task.class), new SimpleModelRuleDescriptor("BasePlugin#configureAssemble"), new Action<Task>() {
             @Override
             public void execute(Task task) {
                 task.dependsOn(project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).getAllArtifacts().getBuildDependencies());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
index 9a11387..261e843 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
@@ -21,6 +21,9 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.java.DefaultJavaSourceSet;
+import org.gradle.api.internal.java.DefaultJvmResourceSet;
+import org.gradle.api.internal.jvm.ClassDirectoryBinarySpecInternal;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.SourceSetCompileClasspath;
@@ -38,9 +41,7 @@ import org.gradle.language.base.FunctionalSourceSet;
 import org.gradle.language.base.ProjectSourceSet;
 import org.gradle.language.base.internal.DefaultFunctionalSourceSet;
 import org.gradle.language.base.plugins.LifecycleBasePlugin;
-import org.gradle.api.internal.java.DefaultJavaSourceSet;
 import org.gradle.language.jvm.JvmResourceSet;
-import org.gradle.api.internal.java.DefaultJvmResourceSet;
 import org.gradle.platform.base.BinaryContainer;
 import org.gradle.util.WrapUtil;
 
@@ -137,7 +138,7 @@ public class JavaBasePlugin implements Plugin<ProjectInternal> {
                 functionalSourceSet.add(resourceSet);
 
                 BinaryContainer binaryContainer = project.getExtensions().getByType(BinaryContainer.class);
-                ClassDirectoryBinarySpec binary = binaryContainer.create(String.format("%sClasses", sourceSet.getName()), ClassDirectoryBinarySpec.class);
+                ClassDirectoryBinarySpecInternal binary = (ClassDirectoryBinarySpecInternal) binaryContainer.create(String.format("%sClasses", sourceSet.getName()), ClassDirectoryBinarySpec.class);
                 ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
                 conventionMapping.map("classesDir", new Callable<File>() {
                     public File call() throws Exception {
@@ -150,8 +151,8 @@ public class JavaBasePlugin implements Plugin<ProjectInternal> {
                     }
                 });
 
-                binary.getSource().add(javaSourceSet);
-                binary.getSource().add(resourceSet);
+                binary.addSourceSet(javaSourceSet);
+                binary.addSourceSet(resourceSet);
 
                 binary.builtBy(sourceSet.getOutput().getDirs());
             }
@@ -313,4 +314,4 @@ public class JavaBasePlugin implements Plugin<ProjectInternal> {
         test.workingDir(project.getProjectDir());
     }
 
-}
\ No newline at end of file
+}
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
index dff5978..e7bcae9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/GroovyRuntime.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/GroovyRuntime.java
@@ -19,8 +19,10 @@ import com.google.common.collect.Lists;
 import org.gradle.api.*;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection;
 import org.gradle.api.internal.plugins.GroovyJarFile;
+import org.gradle.internal.Cast;
 
 import java.io.File;
 import java.util.Collections;
@@ -58,10 +60,9 @@ public class GroovyRuntime {
     }
 
     /**
-     * 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.
+     * 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.
      *
@@ -73,14 +74,14 @@ public class GroovyRuntime {
         // would differ in at least the following ways: 1. live 2. no autowiring
         return new LazilyInitializedFileCollection() {
             @Override
-            public FileCollection createDelegate() {
+            public FileCollectionInternal 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());
+                    return Cast.cast(FileCollectionInternal.class, project.files(groovyJar.getFile()));
                 }
 
                 if (project.getRepositories().isEmpty()) {
@@ -95,7 +96,7 @@ public class GroovyRuntime {
                     // add groovy-ant to bring in Groovydoc
                     dependencies.add(project.getDependencies().create(notation.replace(":groovy:", ":groovy-ant:")));
                 }
-                return project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[dependencies.size()]));
+                return Cast.cast(FileCollectionInternal.class, 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)
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 e9c75c7..a8ce402 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
@@ -15,19 +15,17 @@
  */
 
 package org.gradle.api.distribution.plugins
-
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.distribution.DistributionContainer
 import org.gradle.api.tasks.Sync
 import org.gradle.api.tasks.TaskDependencyMatchers
-import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.tasks.bundling.Zip
 import org.gradle.util.TestUtil
 import spock.lang.Specification
 
-import static org.gradle.util.Matchers.dependsOn
-
 class DistributionPluginTest extends Specification {
     private final Project project = TestUtil.builder().withName("test-project").build()
 
@@ -139,6 +137,15 @@ class DistributionPluginTest extends Specification {
         task.destinationDir == project.file("build/install/test-project-custom")
     }
 
+    def "adds assembleDist task for main distribution"() {
+        when:
+        project.pluginManager.apply(DistributionPlugin)
+
+        then:
+        def task = project.assembleDist
+        task.dependsOn.findAll {it instanceof Task}.collect{ it.path }.containsAll([":distTar", ":distZip"])
+    }
+
     public void "distribution name is configurable"() {
         when:
         project.pluginManager.apply(DistributionPlugin)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/java/DefaultJavaSourceSetTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/java/DefaultJavaSourceSetTest.groovy
index 64bacee..47880e5 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/java/DefaultJavaSourceSetTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/java/DefaultJavaSourceSetTest.groovy
@@ -20,10 +20,11 @@ import spock.lang.Specification
 
 class DefaultJavaSourceSetTest extends Specification {
     def "has useful String representation"() {
-        def resourceSet = new DefaultJavaSourceSet("javaX", "mainX", Stub(SourceDirectorySet), Stub(Classpath))
+        def sourceSet = new DefaultJavaSourceSet("javaX", "mainX", Stub(SourceDirectorySet), Stub(Classpath))
 
         expect:
-        resourceSet.displayName == "Java source 'mainX:javaX'"
-        resourceSet.toString() == "Java source 'mainX:javaX'"
+        sourceSet.displayName == "Java source 'mainX:javaX'"
+        sourceSet.toString() == "Java source 'mainX:javaX'"
     }
+
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy
index 0054954..0d118af 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy
@@ -18,15 +18,18 @@ package org.gradle.api.internal.tasks
 import org.gradle.api.internal.file.DefaultSourceDirectorySet
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.testfixtures.internal.NativeServicesTestFixture
+import org.junit.Before
 import org.junit.Test
+
 import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
 class DefaultGroovySourceSetTest {
-    private final DefaultGroovySourceSet sourceSet = new DefaultGroovySourceSet("<set-display-name>", [resolve: {it as File}] as FileResolver)
+    private final DefaultGroovySourceSet sourceSet = new DefaultGroovySourceSet("<set-display-name>", [resolve: { it as File }] as FileResolver)
 
-    static {
+    @Before
+    void before() {
         NativeServicesTestFixture.initialize()
     }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy
index 476c509..f538bd3 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing.detection
 
+import org.gradle.api.Project
 import org.gradle.api.file.FileCollection
 import org.gradle.api.file.FileTree
 import org.gradle.api.internal.tasks.testing.TestFramework
@@ -37,6 +38,7 @@ class DefaultTestExecuterTest extends Specification {
     TestFrameworkDetector testFrameworkTestDetector = Mock()
     File testClassesDir = Mock()
     FileCollection testClasspath = Mock()
+    Project project = Mock()
 
     DefaultTestExecuter executer = new DefaultTestExecuter(workerFactory, actorFactory)
 
@@ -44,6 +46,7 @@ class DefaultTestExecuterTest extends Specification {
         _ * testTask.testFramework >> testFramework
         _ * testTask.getCandidateClassFiles() >> Mock(FileTree)
         _ * testTask.getPath() >> ':'
+        _ * testTask.getProject() >> project
         _ * actorFactory.createActor(_) >> resultProcessorActor
         _ * resultProcessorActor.getProxy(_) >> resultProcessor
         _ * testTask.isScanForTestClasses() >> true
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
index f7e4b91..045c4e0 100644
--- 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
@@ -50,6 +50,9 @@ class TestOutputStoreSpec extends WorkspaceTest {
         collectAllOutput(reader, 1, StdOut) == "[out-1][out-2][out-5][out-6]"
         collectAllOutput(reader, 1, StdErr) == "[out-4]"
         collectAllOutput(reader, 2, StdErr) == "[out-3]"
+
+        cleanup:
+        reader.close()
     }
 
     def "output for test includes all events with the given class and method ids"() {
@@ -68,6 +71,9 @@ class TestOutputStoreSpec extends WorkspaceTest {
         collectOutput(reader, 1, 1, StdOut) == "[out-2][out-5]"
         collectOutput(reader, 1, 1, StdErr) == "[out-4]"
         collectOutput(reader, 1, 2, StdOut) == "[out-6]"
+
+        cleanup:
+        reader.close()
     }
 
     def "non-test output includes all events with the given class id and no method id"() {
@@ -87,6 +93,9 @@ class TestOutputStoreSpec extends WorkspaceTest {
         collectOutput(reader, 1, StdOut) == "[out-1][out-5]"
         collectOutput(reader, 1, StdErr) == "[out-3][out-4]"
         collectOutput(reader, 2, StdOut) == "[out-6]"
+
+        cleanup:
+        reader.close()
     }
 
     def DefaultTestOutputEvent output(TestOutputEvent.Destination destination, String msg) {
@@ -97,10 +106,13 @@ class TestOutputStoreSpec extends WorkspaceTest {
         when:
         def writer = output.writer()
         writer.close()
+        def reader = output.reader()
 
         then:
-        def reader = output.reader()
         collectAllOutput(reader, 20, StdErr) == ""
+
+        cleanup:
+        reader.close()
     }
 
     def "writes nothing for unknown test method"() {
@@ -108,10 +120,13 @@ class TestOutputStoreSpec extends WorkspaceTest {
         def writer = output.writer()
         writer.onOutput(1, 1, output(StdOut, "[out]"))
         writer.close()
+        def reader = output.reader()
 
         then:
-        def reader = output.reader()
         collectOutput(reader, 1, 10, StdOut) == ""
+
+        cleanup:
+        reader.close()
     }
 
     def "can query whether output is available for a test class"() {
@@ -139,25 +154,19 @@ class TestOutputStoreSpec extends WorkspaceTest {
     def "exception if no output file"() {
         when:
         output.indexFile.createNewFile()
-        def reader = output.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()
+        output.reader()
 
         then:
         thrown(IllegalStateException)
-
-        cleanup:
-        reader?.close()
     }
 
     String collectAllOutput(TestOutputStore.Reader reader, long classId, TestOutputEvent.Destination destination) {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy
index bc24c61..bd2d86d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy
@@ -22,6 +22,7 @@ class SimpleTestDescriptor implements TestDescriptorInternal {
     String className = "ClassName"
     boolean composite = false
     TestDescriptorInternal parent = null
+    Object ownerBuildOperationId = null
     Object getId() {
         "${parent?.id}$className$name" as String
     }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
index 35c69da..85a69b4 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
@@ -33,7 +33,7 @@ class TestMainActionTest {
     private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class)
     private final Runnable detector = context.mock(Runnable.class)
     private final TimeProvider timeProvider = context.mock(TimeProvider.class)
-    private final TestMainAction action = new TestMainAction(detector, processor, resultProcessor, timeProvider, "123", "Test Run")
+    private final TestMainAction action = new TestMainAction(detector, processor, resultProcessor, timeProvider, "taskOperationId123", "rootTestSuiteId456", "Test Run")
 
     @Test
     public void firesStartAndEndEventsAroundDetectorExecution() {
@@ -42,7 +42,7 @@ class TestMainActionTest {
             will(returnValue(100L))
             one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
             will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id as String, equalTo("123"))
+                assertThat(suite.id as String, equalTo("rootTestSuiteId456"))
                 assertThat(event.startTime, equalTo(100L))
             }
             one(processor).startProcessing(withParam(notNullValue()))
@@ -50,9 +50,9 @@ class TestMainActionTest {
             one(processor).stop()
             one(timeProvider).getCurrentTime()
             will(returnValue(200L))
-            one(resultProcessor).completed(withParam(equalTo("123")), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(id as String, equalTo("123"))
+            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
+            will { Object id, TestCompleteEvent event ->
+                assertThat(id as String, equalTo("rootTestSuiteId456"))
                 assertThat(event.endTime, equalTo(200L))
                 assertThat(event.resultType, nullValue())
             }
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 365f054..741fa49 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
@@ -28,6 +28,7 @@ import org.gradle.api.tasks.testing.Test
 import org.gradle.jvm.ClassDirectoryBinarySpec
 import org.gradle.language.java.JavaSourceSet
 import org.gradle.language.jvm.JvmResourceSet
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.SetSystemProperties
 import org.gradle.util.TestUtil
 import org.junit.Rule
@@ -56,7 +57,9 @@ class JavaBasePluginTest extends Specification {
         when:
         project.pluginManager.apply(JavaBasePlugin)
         project.sourceSets.create('custom')
-        
+        new TestFile(project.file("src/custom/java/File.java")) << "foo"
+        new TestFile(project.file("src/custom/resouces/resource.txt")) << "foo"
+
         then:
         SourceSet set = project.sourceSets.custom
         set.java.srcDirs == toLinkedSet(project.file('src/custom/java'))
@@ -69,7 +72,7 @@ class JavaBasePluginTest extends Specification {
         TaskDependencyMatchers.dependsOn().matches(processResources)
         processResources.destinationDir == project.sourceSets.custom.output.resourcesDir
         def resources = processResources.source
-        resources sameCollection(project.sourceSets.custom.resources)
+        resources.files == project.sourceSets.custom.resources.files
 
         def compileJava = project.tasks['compileCustomJava']
         compileJava.description == "Compiles Java source 'custom:java'."
@@ -79,7 +82,7 @@ class JavaBasePluginTest extends Specification {
         compileJava.destinationDir == project.sourceSets.custom.output.classesDir
 
         def sources = compileJava.source
-        sources sameCollection(project.sourceSets.custom.java)
+        sources.files == project.sourceSets.custom.java.files
 
         def classes = project.tasks['customClasses']
         classes.description == "Assembles classes 'custom'."
@@ -264,4 +267,4 @@ class JavaBasePluginTest extends Specification {
         binary.resourcesDir == project.file("resources")
         binary.source as Set == project.sources as Set
     }
-}
\ No newline at end of file
+}
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 195d108..9e917ff 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
@@ -31,6 +31,7 @@ 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.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TestUtil
 import org.junit.Rule
@@ -87,7 +88,7 @@ class JavaPluginTest {
 
     @Test public void addsJarAsPublication() {
         javaPlugin.apply(project)
-        
+
         def runtimeConfiguration = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
         assertThat(runtimeConfiguration.artifacts.collect { it.archiveTask }, equalTo([project.tasks.getByName(JavaPlugin.JAR_TASK_NAME)]))
 
@@ -145,11 +146,15 @@ class JavaPluginTest {
 
     @Test public void createsStandardTasksAndAppliesMappings() {
         javaPlugin.apply(project)
+        new TestFile(project.file("src/main/java/File.java")) << "foo"
+        new TestFile(project.file("src/main/resources/thing.txt")) << "foo"
+        new TestFile(project.file("src/test/java/File.java")) << "foo"
+        new TestFile(project.file("src/test/resources/thing.txt")) << "foo"
 
         def task = project.tasks[JavaPlugin.PROCESS_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
         assertThat(task, TaskDependencyMatchers.dependsOn())
-        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.main.resources))
+        assertEquals(task.source.files, project.sourceSets.main.resources.files)
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_JAVA_TASK_NAME]
@@ -157,8 +162,8 @@ class JavaPluginTest {
         assertThat(task, TaskDependencyMatchers.dependsOn())
         assertThat(task.classpath, sameInstance(project.sourceSets.main.compileClasspath))
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.classesDir))
-        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.main.java))
-        
+        assertEquals(task.source.files, project.sourceSets.main.java.files)
+
         task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
         assertThat(task, TaskDependencyMatchers.dependsOn(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, JavaPlugin.COMPILE_JAVA_TASK_NAME))
@@ -166,7 +171,7 @@ class JavaPluginTest {
         task = project.tasks[JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
         assertThat(task, TaskDependencyMatchers.dependsOn())
-        assertThat(task.source, FileCollectionMatchers.sameCollection(project.sourceSets.test.resources))
+        assertEquals(task.source.files, project.sourceSets.test.resources.files)
         assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME]
@@ -174,7 +179,7 @@ class JavaPluginTest {
         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, FileCollectionMatchers.sameCollection(project.sourceSets.test.java))
+        assertEquals(task.source.files, project.sourceSets.test.java.files)
 
         task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
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 5160f36..df6c413 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
@@ -35,9 +35,9 @@ import org.gradle.api.publish.internal.PublicationInternal;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.model.Model;
+import org.gradle.model.ModelMap;
 import org.gradle.model.Mutate;
 import org.gradle.model.RuleSource;
-import org.gradle.model.collection.CollectionBuilder;
 
 import javax.inject.Inject;
 
@@ -93,7 +93,7 @@ public class PublishingPlugin implements Plugin<Project> {
         }
 
         @Mutate
-        void tasksDependOnProjectPublicationRegistry(CollectionBuilder<Task> tasks, ProjectPublicationRegistry publicationRegistry) {
+        void tasksDependOnProjectPublicationRegistry(ModelMap<Task> tasks, ProjectPublicationRegistry publicationRegistry) {
             //do nothing, the rule is here to introduce a dependency on ProjectPublicationRegistry to TaskContainer
         }
     }
diff --git a/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategy.java b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategy.java
new file mode 100644
index 0000000..9e8b616
--- /dev/null
+++ b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.transport.http;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolException;
+import org.apache.http.client.methods.*;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.protocol.HttpContext;
+
+import java.net.URI;
+
+public class AlwaysRedirectRedirectStrategy extends DefaultRedirectStrategy {
+
+    public AlwaysRedirectRedirectStrategy() {
+    }
+
+    @Override
+    protected boolean isRedirectable(String method) {
+        return true;
+    }
+
+    public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
+        URI uri = this.getLocationURI(request, response, context);
+        String method = request.getRequestLine().getMethod();
+        if (method.equalsIgnoreCase("HEAD")) {
+            return new HttpHead(uri);
+        } else if (method.equalsIgnoreCase("POST")) {
+            return this.copyEntity(new HttpPost(uri), request);
+        } else if (method.equalsIgnoreCase("PUT")) {
+            return this.copyEntity(new HttpPut(uri), request);
+        } else if (method.equalsIgnoreCase("DELETE")) {
+            return new HttpDelete(uri);
+        } else if (method.equalsIgnoreCase("TRACE")) {
+            return new HttpTrace(uri);
+        } else if (method.equalsIgnoreCase("OPTIONS")) {
+            return new HttpOptions(uri);
+        } else if (method.equalsIgnoreCase("PATCH")) {
+            return this.copyEntity(new HttpPatch(uri), request);
+        } else {
+            return new HttpGet(uri);
+        }
+    }
+
+    private HttpUriRequest copyEntity(HttpEntityEnclosingRequestBase redirect, HttpRequest original) {
+        if (original instanceof HttpEntityEnclosingRequest) {
+            redirect.setEntity(((HttpEntityEnclosingRequest) original).getEntity());
+        }
+        return redirect;
+    }
+}
diff --git a/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/HttpClientHelper.java b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/HttpClientHelper.java
index 8412a76..0dc32b9 100644
--- a/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/HttpClientHelper.java
+++ b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/HttpClientHelper.java
@@ -21,7 +21,10 @@ import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.impl.client.*;
+import org.apache.http.impl.client.DecompressingHttpClient;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.impl.client.SystemDefaultHttpClient;
 import org.apache.http.protocol.BasicHttpContext;
 import org.apache.http.util.EntityUtils;
 import org.gradle.api.UncheckedIOException;
@@ -41,8 +44,8 @@ public class HttpClientHelper {
 
     public HttpClientHelper(HttpSettings settings) {
         alwaysUseKeepAliveConnections();
-
         DefaultHttpClient client = new SystemDefaultHttpClient();
+        client.setRedirectStrategy(new AlwaysRedirectRedirectStrategy());
         new HttpClientConfigurer(settings).configure(client);
         this.client = new DecompressingHttpClient(client);
     }
@@ -55,9 +58,9 @@ public class HttpClientHelper {
     }
 
     public HttpResponse performRawHead(String source) {
-        return performRequest(new HttpHead(source));        
+        return performRequest(new HttpHead(source));
     }
-    
+
     public HttpResponse performHead(String source) {
         return processResponse(source, "HEAD", performRawHead(source));
     }
@@ -100,13 +103,12 @@ public class HttpClientHelper {
 
     public boolean wasSuccessful(HttpResponse response) {
         int statusCode = response.getStatusLine().getStatusCode();
-        return statusCode >= 200 && statusCode < 300;
+        return statusCode >= 200 && statusCode < 400;
     }
 
     public HttpResponse performHttpRequest(HttpRequestBase request) throws IOException {
         // Without this, HTTP Client prohibits multiple redirects to the same location within the same context
         httpContext.removeAttribute(DefaultRedirectStrategy.REDIRECT_LOCATIONS);
-
         LOGGER.debug("Performing HTTP {}: {}", request.getMethod(), request.getURI());
         return client.execute(request, httpContext);
     }
@@ -119,11 +121,9 @@ public class HttpClientHelper {
         if (!wasSuccessful(response)) {
             LOGGER.info("Failed to get resource: {}. [HTTP {}: {}]", method, response.getStatusLine(), source);
             throw new UncheckedIOException(String.format("Could not %s '%s'. Received status code %s from server: %s",
-                    method, source, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
+                method, source, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
         }
 
         return response;
     }
-
-
 }
diff --git a/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategyTest.groovy b/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategyTest.groovy
new file mode 100644
index 0000000..ff36541
--- /dev/null
+++ b/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/AlwaysRedirectRedirectStrategyTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.transport.http
+
+import org.apache.http.HttpRequest
+import org.apache.http.HttpResponse
+import org.apache.http.RequestLine
+import org.apache.http.message.BasicHeader
+import org.apache.http.params.HttpParams
+import org.apache.http.protocol.HttpContext
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class AlwaysRedirectRedirectStrategyTest extends Specification {
+
+    static final String[] HTTP_METHODS = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE', 'PATCH']
+
+    def "should consider all requests redirectable"() {
+        expect:
+        new AlwaysRedirectRedirectStrategy().isRedirectable(method)
+
+        where:
+        method << HTTP_METHODS
+    }
+
+
+    @Unroll
+    def "should get redirect for http method [#httpMethod]"() {
+        setup:
+        HttpRequest request = Mock()
+        HttpResponse response = Mock()
+        HttpContext context = Mock()
+        response.getFirstHeader("location") >> new BasicHeader('location', 'http://redirectTo')
+        request.getRequestLine() >> Mock(RequestLine) {
+            getMethod() >> httpMethod
+        }
+        request.getParams() >> Mock(HttpParams)
+
+        when:
+        def redirect = new AlwaysRedirectRedirectStrategy().getRedirect(request, response, context)
+
+        then:
+        redirect.getClass() == Class.forName("org.apache.http.client.methods.Http${httpMethod.toLowerCase().capitalize()}")
+
+        where:
+        httpMethod << HTTP_METHODS + HTTP_METHODS.collect{it.toLowerCase()}
+    }
+}
diff --git a/subprojects/resources-s3/src/integTest/groovy/org/gradle/integtests/resource/s3/maven/MavenPublishS3ErrorsIntegrationTest.groovy b/subprojects/resources-s3/src/integTest/groovy/org/gradle/integtests/resource/s3/maven/MavenPublishS3ErrorsIntegrationTest.groovy
index e3090c7..b9d06ce 100644
--- a/subprojects/resources-s3/src/integTest/groovy/org/gradle/integtests/resource/s3/maven/MavenPublishS3ErrorsIntegrationTest.groovy
+++ b/subprojects/resources-s3/src/integTest/groovy/org/gradle/integtests/resource/s3/maven/MavenPublishS3ErrorsIntegrationTest.groovy
@@ -69,6 +69,7 @@ class MavenPublishS3ErrorsIntegrationTest extends AbstractIntegrationSpec {
         when:
         def module = mavenS3Repo.module("org.gradle", "publishS3Test", "1.45")
         module.artifact.expectPutAuthencicationError()
+        module.pom.expectPutAuthencicationError()
 
         then:
         fails 'publish'
diff --git a/subprojects/resources/src/test/groovy/org/gradle/internal/resource/UriResourceTest.groovy b/subprojects/resources/src/test/groovy/org/gradle/internal/resource/UriResourceTest.groovy
index 637d168..498fe45 100644
--- a/subprojects/resources/src/test/groovy/org/gradle/internal/resource/UriResourceTest.groovy
+++ b/subprojects/resources/src/test/groovy/org/gradle/internal/resource/UriResourceTest.groovy
@@ -19,6 +19,7 @@ package org.gradle.internal.resource
 
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.TestPrecondition
 import org.junit.Assume
 import org.junit.Before
@@ -114,7 +115,7 @@ class UriResourceTest {
             assertThat(e.message, equalTo("Could not read <display-name> '$dir' as it is a directory." as String))
         }
     }
-    
+
     @Test
     public void readsFileContentUsingFileUriWhenFileExists() {
         file.text = '<content>'
@@ -137,6 +138,7 @@ class UriResourceTest {
     }
 
     @Test
+    @LeaksFileHandles
     public void readsFileContentUsingJarUriWhenFileExists() {
         file.text = '<content>'
 
@@ -146,6 +148,7 @@ class UriResourceTest {
     }
 
     @Test
+    @LeaksFileHandles
     public void hasNoContentWhenUsingJarUriAndFileDoesNotExistInJar() {
         URI jarUri = createJar()
         UriResource resource = new UriResource('<display-name>', jarUri);
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingOlderScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingOlderScalaCompilerIntegrationTest.groovy
index fe9ce1d..0b54ffa 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingOlderScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingOlderScalaCompilerIntegrationTest.groovy
@@ -18,9 +18,11 @@ package org.gradle.scala.compile
 
 import org.gradle.integtests.fixtures.ScalaCoverage
 import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @TargetCoverage({ScalaCoverage.OLDER})
 @Requires(TestPrecondition.JDK7_OR_EARLIER)
+ at LeaksFileHandles
 class AntForkingOlderScalaCompilerIntegrationTest extends AbstractAntForkingScalaCompilerIntegrationTest {}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
index 559b6cc..d627b63 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
@@ -17,6 +17,8 @@
 package org.gradle.scala.compile
 import org.gradle.integtests.fixtures.ScalaCoverage
 import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 @TargetCoverage({ScalaCoverage.DEFAULT})
-class AntForkingScalaCompilerIntegrationTest extends AbstractAntForkingScalaCompilerIntegrationTest {}
\ No newline at end of file
+ at LeaksFileHandles
+class AntForkingScalaCompilerIntegrationTest extends AbstractAntForkingScalaCompilerIntegrationTest {}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessOlderScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessOlderScalaCompilerIntegrationTest.groovy
index 23daf81..cb4ba96 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessOlderScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessOlderScalaCompilerIntegrationTest.groovy
@@ -18,9 +18,11 @@ package org.gradle.scala.compile
 
 import org.gradle.integtests.fixtures.ScalaCoverage
 import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 @TargetCoverage({ScalaCoverage.DEFAULT})
 @Requires(TestPrecondition.JDK7_OR_EARLIER)
+ at LeaksFileHandles
 class AntInProcessOlderScalaCompilerIntegrationTest extends AbstractAntInProcessScalaCompilerIntegrationTest {}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
index a1dee7f..73dffc9 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
@@ -18,6 +18,8 @@ package org.gradle.scala.compile
 
 import org.gradle.integtests.fixtures.ScalaCoverage
 import org.gradle.integtests.fixtures.TargetCoverage
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 @TargetCoverage({ScalaCoverage.DEFAULT})
+ at LeaksFileHandles
 class AntInProcessScalaCompilerIntegrationTest extends AbstractAntInProcessScalaCompilerIntegrationTest {}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
index 1899eb2..3c075e4 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
@@ -18,9 +18,11 @@ package org.gradle.scala.compile
 import org.gradle.integtests.fixtures.ScalaCoverage
 import org.gradle.integtests.fixtures.TargetCoverage
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 
 @TargetCoverage({ScalaCoverage.DEFAULT})
+ at LeaksFileHandles
 class ZincScalaCompilerIntegrationTest extends BasicScalaCompilerIntegrationTest {
     @Rule TestResources testResources = new TestResources(temporaryFolder)
 
@@ -95,4 +97,4 @@ compileScala.scalaCompileOptions.with {
         house.lastModified() != old(house.lastModified())
         other.lastModified() == old(other.lastModified())
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.java
index 218eaa9..380beb7 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/ScalaRuntime.java
@@ -18,7 +18,9 @@ package org.gradle.api.tasks;
 import org.gradle.api.*;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.file.FileCollectionInternal;
 import org.gradle.api.internal.file.collections.LazilyInitializedFileCollection;
+import org.gradle.internal.Cast;
 
 import java.io.File;
 import java.util.Collections;
@@ -77,7 +79,7 @@ public class ScalaRuntime {
         // 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
-            public FileCollection createDelegate() {
+            public FileCollectionInternal createDelegate() {
                 if (project.getRepositories().isEmpty()) {
                     throw new GradleException(String.format("Cannot infer Scala class path because no repository is declared in %s", project));
                 }
@@ -93,8 +95,8 @@ public class ScalaRuntime {
                     throw new AssertionError(String.format("Unexpectedly failed to parse version of Scala Jar file: %s in %s", scalaLibraryJar, project));
                 }
 
-                return project.getConfigurations().detachedConfiguration(
-                        new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion));
+                return Cast.cast(FileCollectionInternal.class, project.getConfigurations().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)
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy
index ffd9b4a..68ea277 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy
@@ -18,17 +18,21 @@ package org.gradle.api.internal.tasks
 import org.gradle.api.internal.file.DefaultSourceDirectorySet
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.testfixtures.internal.NativeServicesTestFixture
+import org.junit.Before
 import org.junit.Test
+
 import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
 class DefaultScalaSourceSetTest {
-    static {
+
+    @Before
+    void before() {
         NativeServicesTestFixture.initialize()
     }
 
-    private final DefaultScalaSourceSet sourceSet = new DefaultScalaSourceSet("<set-display-name>", [resolve: {it as File}] as FileResolver)
+    private final DefaultScalaSourceSet sourceSet = new DefaultScalaSourceSet("<set-display-name>", [resolve: { it as File }] as FileResolver)
 
     @Test
     public void defaultValues() {
@@ -51,4 +55,4 @@ class DefaultScalaSourceSetTest {
         sourceSet.scala { srcDir 'src/scala' }
         assertThat(sourceSet.scala.srcDirs, equalTo([new File('src/scala').canonicalFile] as Set))
     }
-}
\ No newline at end of file
+}
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 4425e54..7a542fe 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
@@ -19,6 +19,7 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.FileTreeInternal;
 import org.gradle.api.internal.tasks.scala.ScalaJavaJointCompileSpec;
 import org.gradle.api.tasks.TaskExecutionException;
 import org.gradle.api.tasks.compile.AbstractCompile;
@@ -100,10 +101,10 @@ public class ScalaCompileTest extends AbstractCompileTest {
         compile.setSourceCompatibility("1.5");
         compile.setTargetCompatibility("1.5");
         compile.setDestinationDir(destDir);
-        scalaClasspath = context.mock(FileTree.class);
+        scalaClasspath = context.mock(FileTreeInternal.class);
         compile.setScalaClasspath(scalaClasspath);
-        final FileTree classpath = context.mock(FileTree.class);
-        final FileTree zincClasspath = context.mock(FileTree.class);
+        final FileTree classpath = context.mock(FileTreeInternal.class);
+        final FileTree zincClasspath = context.mock(FileTreeInternal.class);
 
         context.checking(new Expectations(){{
             allowing(scalaClasspath).getFiles(); will(returnValue(new HashSet<File>()));
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 1e7be56..1dec253 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
@@ -22,6 +22,7 @@ import org.gradle.internal.classloader.ClasspathUtil
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.test.fixtures.server.http.ServletContainer
 import org.gradle.util.AvailablePortFinder
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
@@ -67,6 +68,7 @@ sonar.embeddedDatabase.port=$databasePort
         container.start()
     }
 
+    @LeaksFileHandles
     def "can run Sonar analysis"() {
         executer.requireIsolatedDaemons()
         // Without forking, we run into problems with Sonar's BootStrapClassLoader, at least when running from IDEA.
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
index 6d5f6b0..9cf2801 100644
--- a/subprojects/sonar/src/integTest/groovy/org/gradle/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
+++ b/subprojects/sonar/src/integTest/groovy/org/gradle/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
@@ -20,12 +20,14 @@ import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 
 @Requires(TestPrecondition.JDK7_OR_EARLIER)
 @TargetVersions(['default', '2.4'])
+ at LeaksFileHandles
 class SonarRunnerSmokeIntegrationTest extends MultiVersionIntegrationSpec {
 
     @Rule
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/SonarRunnerExtension.java b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/SonarRunnerExtension.java
index 32c2e70..50fd28b 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/SonarRunnerExtension.java
+++ b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/SonarRunnerExtension.java
@@ -64,9 +64,9 @@ public class SonarRunnerExtension {
     }
 
     /**
-     * Adds an action that configures Sonar properties for the associated Gradle project.
+     * Adds an action that configures SonarQube properties for the associated Gradle project.
      * <p>
-     * <em>Global</em> Sonar properties (e.g. database connection settings) have to be set on the "root" project of the Sonar run.
+     * <em>Global</em> SonarQube properties (e.g. database connection settings) have to be set on the "root" project of the Sonar run.
      * This is the project that has the {@code sonar-runner} plugin applied.
      * <p>
      * The action is passed an instance of {@code SonarProperties}.
@@ -74,8 +74,8 @@ public class SonarRunnerExtension {
      * Hence it is safe to reference other Gradle model properties from inside the action.
      * <p>
      * Sonar properties can also be set via system properties (and therefore from the command line).
-     * This is mainly useful for global Sonar properties like database credentials.
-     * Every system property starting with {@code "sonar."} is automatically set on the "root" project of the Sonar run (i.e. the project that has the {@code sonar-runner} plugin applied).
+     * This is mainly useful for global SonarQube properties like database credentials.
+     * Every system property starting with {@code "sonar."} is automatically set on the "root" project of the SonarQube run (i.e. the project that has the {@code sonar-runner} plugin applied).
      * System properties take precedence over properties declared in build scripts.
      *
      * @param action an action that configures Sonar properties for the associated Gradle project
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/plugins/SonarRunnerPlugin.java b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/plugins/SonarRunnerPlugin.java
index d795b01..bc56e15 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/plugins/SonarRunnerPlugin.java
+++ b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/plugins/SonarRunnerPlugin.java
@@ -27,7 +27,6 @@ import org.gradle.api.*;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.DependencySet;
-import org.gradle.api.artifacts.ResolvableDependencies;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.plugins.DslObject;
@@ -55,11 +54,11 @@ import java.util.concurrent.Callable;
 import static org.gradle.util.CollectionUtils.nonEmptyOrNull;
 
 /**
- * A plugin for analyzing projects with the <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+SonarQube+Runner">Sonar Runner</a>.
+ * A plugin for analyzing projects with the <a href="http://redirect.sonarsource.com/doc/analyzing-with-sq-runner.html">SonarQube Runner</a>.
  * <p>
  * When applied to a project, both the project itself and its subprojects will be analyzed (in a single run).
  * <p>
- * Please see the “Sonar Runner Plugin” chapter of the Gradle User Guide for more information.
+ * Please see the “SonarQube Runner Plugin” chapter of the Gradle User Guide for more information.
  */
 @Incubating
 public class SonarRunnerPlugin implements Plugin<Project> {
@@ -261,7 +260,7 @@ public class SonarRunnerPlugin implements Plugin<Project> {
     }
 
     private String getProjectKey(Project project) {
-        // Sonar uses project keys in URL parameters without internally URL-encoding them.
+        // SonarQube uses project keys in URL parameters without internally URL-encoding them.
         // According to my manual tests with sonar-runner plugin based on Sonar Runner 2.0 and Sonar 3.4.1,
         // the current defaults will only cause a problem if project.group or project.name of
         // the Gradle project to which the plugin is applied contains special characters.
@@ -328,16 +327,13 @@ public class SonarRunnerPlugin implements Plugin<Project> {
                 .setVisible(false)
                 .setTransitive(false)
                 .setDescription("The SonarRunner configuration to use to run analysis")
-                .getIncoming()
-                .beforeResolve(new Action<ResolvableDependencies>() {
-                    public void execute(ResolvableDependencies resolvableDependencies) {
-                        DependencySet dependencies = resolvableDependencies.getDependencies();
-                        if (dependencies.isEmpty()) {
-                            String toolVersion = rootExtension.getToolVersion();
-                            DependencyHandler dependencyHandler = project.getDependencies();
-                            Dependency dependency = dependencyHandler.create("org.codehaus.sonar.runner:sonar-runner-dist:" + toolVersion);
-                            configuration.getDependencies().add(dependency);
-                        }
+                .defaultDependencies(new Action<DependencySet>() {
+                    @Override
+                    public void execute(DependencySet dependencies) {
+                        String toolVersion = rootExtension.getToolVersion();
+                        DependencyHandler dependencyHandler = project.getDependencies();
+                        Dependency dependency = dependencyHandler.create("org.codehaus.sonar.runner:sonar-runner-dist:" + toolVersion);
+                        dependencies.add(dependency);
                     }
                 });
     }
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/tasks/SonarRunner.java b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/tasks/SonarRunner.java
index 521bc26..d5f979d 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/tasks/SonarRunner.java
+++ b/subprojects/sonar/src/main/groovy/org/gradle/sonar/runner/tasks/SonarRunner.java
@@ -38,15 +38,15 @@ import java.util.Map;
 import java.util.Properties;
 
 /**
- * Analyses one or more projects with the <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner">Sonar Runner</a>.
+ * Analyses one or more projects with the <a href="http://redirect.sonarsource.com/doc/analyzing-with-sq-runner.html">SonarQube Runner</a>.
  * <p>
  * Can be used with or without the {@code "sonar-runner"} plugin.
  * If used together with the plugin, {@code sonarProperties} will be populated with defaults based on Gradle's object model and user-defined
  * values configured via {@link SonarRunnerExtension} and {@link org.gradle.sonar.runner.SonarRunnerRootExtension}.
  * If used without the plugin, all properties have to be configured manually.
  * <p>
- * For more information on how to configure the Sonar Runner, and on which properties are available, see the
- * <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+SonarQube+Runner">Sonar Runner documentation</a>.
+ * For more information on how to configure the SonarQube Runner, and on which properties are available, see the
+ * <a href="http://redirect.sonarsource.com/doc/analyzing-with-sq-runner.html">SonarQube Runner documentation</a>.
  */
 @Incubating
 public class SonarRunner extends DefaultTask {
@@ -65,11 +65,11 @@ public class SonarRunner extends DefaultTask {
     JavaExecHandleBuilder prepareExec() {
         Map<String, Object> properties = getSonarProperties();
         if(getProject().file("sonar-project.properties").exists()){
-            LOGGER.warn("Found 'sonar-project.properties' in project directory: Sonar Runner may read this file to override the Gradle 'sonarRunner' configuration.");
+            LOGGER.warn("Found 'sonar-project.properties' in project directory: SonarQube Runner may read this file to override the Gradle 'sonarRunner' configuration.");
         }
 
         if (LOGGER.isInfoEnabled()) {
-            LOGGER.info("Executing Sonar Runner with properties:\n[{}]", Joiner.on(", ").withKeyValueSeparator(": ").join(properties));
+            LOGGER.info("Executing SonarQube Runner with properties:\n[{}]", Joiner.on(", ").withKeyValueSeparator(": ").join(properties));
         }
 
         JavaExecHandleBuilder javaExec = new JavaExecHandleBuilder(getFileResolver());
@@ -85,7 +85,7 @@ public class SonarRunner extends DefaultTask {
         return javaExec
                 .systemProperty("project.settings", propertyFile.getAbsolutePath())
 
-                // This value is set in the properties file, but Sonar Runner 2.4 requires it on the command line as well
+                // This value is set in the properties file, but SonarQube Runner 2.4 requires it on the command line as well
                 // http://forums.gradle.org/gradle/topics/gradle-2-2-nightly-sonarrunner-task-fails-with-toolversion-2-4
                 .systemProperty("project.home", getProject().getProjectDir().getAbsolutePath())
 
diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/CUnitIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/CUnitIntegrationTest.groovy
index 20e8a0e..d857b12 100755
--- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/CUnitIntegrationTest.groovy
+++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/CUnitIntegrationTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.nativeplatform.test.cunit
 
+import org.gradle.api.reporting.model.ConsoleReportOutput
 import org.gradle.ide.visualstudio.fixtures.ProjectFile
 import org.gradle.ide.visualstudio.fixtures.SolutionFile
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
@@ -22,13 +23,16 @@ import org.gradle.internal.os.OperatingSystem
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.AvailableToolChains
 import org.gradle.nativeplatform.fixtures.app.CHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.gradle.util.TextUtil
+import spock.lang.Issue
 
-import static org.gradle.util.TextUtil.normaliseLineSeparators
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class CUnitIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def prebuiltPath = TextUtil.normaliseFileSeparators(new IntegrationTestBuildContext().getSamplesDir().file("native-binaries/cunit/libs").path)
@@ -113,7 +117,7 @@ binaries.withType(CUnitTestSuiteBinarySpec) {
 
         then:
         executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestC",
-                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+            ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
         file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
 
         def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
@@ -124,6 +128,22 @@ binaries.withType(CUnitTestSuiteBinarySpec) {
         testResults.checkAssertions(3, 3, 0)
     }
 
+    @Issue("GRADLE-3225")
+    def "can build and run cunit test suite with C and C++"() {
+        given:
+        useConventionalSourceLocations()
+        useStandardConfig()
+        buildFile << "apply plugin: 'cpp'"
+        file("src/hello/cpp").createDir().file("foo.cpp").text = "class foobar { };"
+
+        when:
+        run "check"
+
+        then:
+        executedAndNotSkipped ":compileHelloTestCUnitExeHelloCpp", ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestC",
+            ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+    }
+
     def "can configure via testSuite component"() {
         given:
         useConventionalSourceLocations()
@@ -150,7 +170,7 @@ model {
 
         then:
         executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestC",
-                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+            ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
         file("build/test-results/helloTestCUnitExe/CUnitAutomated-Listing.xml").assertExists()
 
         def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
@@ -161,6 +181,38 @@ model {
         testResults.checkAssertions(3, 3, 0)
     }
 
+    def "testSuite components exposed to modelReport"() {
+        given:
+        buildFile << """
+model {
+    components {
+        nativeComponentOne(NativeLibrarySpec)
+        nativeComponentTwo(NativeLibrarySpec)
+    }
+}
+"""
+        when:
+        run "model"
+
+        then:
+        ConsoleReportOutput consoleReportOutput = new ConsoleReportOutput(output)
+        consoleReportOutput.hasNodeStructure("""    testSuites
+        nativeComponentOneTest
+            binaries
+                nativeComponentOneTestCUnitExe
+                    tasks
+            sources
+                c
+                cunitLauncher
+        nativeComponentTwoTest
+            binaries
+                nativeComponentTwoTestCUnitExe
+                    tasks
+            sources
+                c
+                cunitLauncher""")
+    }
+
     def "can supply cCompiler macro to cunit sources"() {
         given:
         useConventionalSourceLocations()
@@ -192,8 +244,7 @@ model {
     testSuites {
         helloTest {
             sources {
-                // TODO:DAZ Should not need type here (source set should already be created)
-                c(CSourceSet) {
+                c {
                     source.srcDir "src/alternateHelloTest/c"
                 }
             }
@@ -218,8 +269,7 @@ model {
     testSuites {
         helloTest {
             sources {
-                // TODO:DAZ Should not need type here (source set should already be created)
-                c(CSourceSet) {
+                c {
                     source.srcDir "src/alternateHelloTest/c"
                 }
             }
@@ -321,10 +371,9 @@ model {
 
         and:
         executedAndNotSkipped ":compileHelloTestCUnitExeHelloC", ":compileHelloTestCUnitExeHelloTestC",
-                              ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
-        output.contains """
-There were test failures:
-"""
+            ":linkHelloTestCUnitExe", ":helloTestCUnitExe", ":runHelloTestCUnitExe"
+        contains "There were test failures:"
+
         and:
         def testResults = new CUnitTestResults(file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml"))
         testResults.suiteNames == ['hello test']
@@ -347,10 +396,8 @@ tasks.withType(RunTestExecutable) {
         succeeds "runHelloTestCUnitExe"
 
         then:
-        output.contains """
-There were test failures:
-"""
-        output.contains "There were failing tests. See the results at: "
+        contains "There were test failures:"
+        contains "There were failing tests. See the results at: "
 
         and:
         file("build/test-results/helloTestCUnitExe/CUnitAutomated-Results.xml").assertExists()
@@ -386,18 +433,19 @@ There were test failures:
         and:
         final projectFile = new ProjectFile(file("helloTestExe.vcxproj"))
         projectFile.sourceFiles as Set == [
-                "build.gradle",
-                "build/src/helloTest/cunitLauncher/c/gradle_cunit_main.c",
-                "src/helloTest/c/test.c",
-                "src/hello/c/hello.c",
-                "src/hello/c/sum.c"
+            "build.gradle",
+            "build/src/helloTest/cunitLauncher/c/gradle_cunit_main.c",
+            "src/helloTest/c/test.c",
+            "src/hello/c/hello.c",
+            "src/hello/c/sum.c"
         ] as Set
         projectFile.headerFiles == [
-                "build/src/helloTest/cunitLauncher/headers/gradle_cunit_register.h",
-                "src/hello/headers/hello.h"
+            "build/src/helloTest/cunitLauncher/headers/gradle_cunit_register.h",
+            "src/hello/headers/common.h",
+            "src/hello/headers/hello.h"
         ]
         projectFile.projectConfigurations.keySet() == ['debug'] as Set
-        with (projectFile.projectConfigurations['debug']) {
+        with(projectFile.projectConfigurations['debug']) {
             includePath == "src/helloTest/headers;build/src/helloTest/cunitLauncher/headers;src/hello/headers;${prebuiltPath}/cunit/2.1-2/include"
         }
     }
@@ -412,8 +460,7 @@ There were test failures:
         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())
+    boolean contains(String content) {
+        return getOutput().contains(toPlatformLineSeparators(content))
     }
 }
diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/ComponentReportIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/ComponentReportIntegrationTest.groovy
index e7988f6..0d13a6f 100644
--- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/ComponentReportIntegrationTest.groovy
+++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/cunit/ComponentReportIntegrationTest.groovy
@@ -15,12 +15,14 @@
  */
 package org.gradle.nativeplatform.test.cunit
 
-import org.gradle.api.reporting.components.AbstractComponentReportIntegrationTest
+import org.gradle.api.reporting.components.NativeComponentReportIntegrationTest
 import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 
-class ComponentReportIntegrationTest extends AbstractComponentReportIntegrationTest {
+class ComponentReportIntegrationTest extends NativeComponentReportIntegrationTest {
     private String currentNative = NativePlatformsTestFixture.defaultPlatformName
 
+    @RequiresInstalledToolChain
     def "shows details of native C executable with test suite"() {
         given:
         buildFile << """
@@ -48,7 +50,7 @@ Native executable 'someExe'
 
 Source sets
     C source 'someExe:c'
-        src/someExe/c
+        srcDir: src/someExe/c
 
 Binaries
     Executable 'someExe:executable'
@@ -65,9 +67,9 @@ Cunit test suite 'someExeTest'
 
 Source sets
     C source 'someExeTest:c'
-        src/someExeTest/c
+        srcDir: src/someExeTest/c
     C source 'someExeTest:cunitLauncher'
-        build/src/someExeTest/cunitLauncher/c
+        srcDir: build/src/someExeTest/cunitLauncher/c
 
 Binaries
     C unit exe 'someExeTest:cUnitExe'
diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/ComponentReportIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/ComponentReportIntegrationTest.groovy
index 9c40971..c401358 100644
--- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/ComponentReportIntegrationTest.groovy
+++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/ComponentReportIntegrationTest.groovy
@@ -15,12 +15,14 @@
  */
 package org.gradle.nativeplatform.test.googletest
 
-import org.gradle.api.reporting.components.AbstractComponentReportIntegrationTest
+import org.gradle.api.reporting.components.NativeComponentReportIntegrationTest
 import org.gradle.nativeplatform.fixtures.NativePlatformsTestFixture
+import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain
 
-class ComponentReportIntegrationTest extends AbstractComponentReportIntegrationTest {
+class ComponentReportIntegrationTest extends NativeComponentReportIntegrationTest {
     private String currentNative = NativePlatformsTestFixture.defaultPlatformName
 
+    @RequiresInstalledToolChain
     def "shows details of native C++ executable with test suite"() {
         given:
         buildFile << """
@@ -48,7 +50,7 @@ Native executable 'someExe'
 
 Source sets
     C++ source 'someExe:cpp'
-        src/someExe/cpp
+        srcDir: src/someExe/cpp
 
 Binaries
     Executable 'someExe:executable'
@@ -65,7 +67,7 @@ GoogleTest test suite 'someExeTest'
 
 Source sets
     C++ source 'someExeTest:cpp'
-        src/someExeTest/cpp
+        srcDir: src/someExeTest/cpp
 
 Binaries
     Google test exe 'someExeTest:googleTestExe'
diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestIntegrationTest.groovy
index 0ef7952..c9f4b18 100755
--- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestIntegrationTest.groovy
+++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestIntegrationTest.groovy
@@ -22,13 +22,16 @@ import org.gradle.internal.os.OperatingSystem
 import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec
 import org.gradle.nativeplatform.fixtures.AvailableToolChains
 import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.gradle.util.TextUtil
+import spock.lang.Issue
 
 import static org.gradle.util.TextUtil.normaliseLineSeparators
 
 @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE)
+ at LeaksFileHandles
 class GoogleTestIntegrationTest extends AbstractInstalledToolChainIntegrationSpec {
 
     def prebuiltPath = TextUtil.normaliseFileSeparators(new IntegrationTestBuildContext().getSamplesDir().file("native-binaries/google-test/libs").path)
@@ -68,14 +71,23 @@ model {
         }
     }
 }
-binaries.withType(GoogleTestTestSuiteBinarySpec) {
-    lib library: "googleTest", linkage: "static"
-}
-
 tasks.withType(RunTestExecutable) {
     args "--gtest_output=xml:test_detail.xml"
 }
 """
+        addGoogleTestDep()
+    }
+
+    private void addGoogleTestDep() {
+        buildFile << """
+binaries.withType(GoogleTestTestSuiteBinarySpec) {
+    lib library: "googleTest", linkage: "static"
+    if (targetPlatform.operatingSystem.linux) {
+        cppCompiler.args '-pthread'
+        linker.args '-pthread'
+    }
+}
+"""
     }
 
     private def getGoogleTestPlatform() {
@@ -126,6 +138,23 @@ tasks.withType(RunTestExecutable) {
         testResults.checkTestCases(1, 1, 0)
     }
 
+    @Issue("GRADLE-3225")
+    def "can build and run googleTest test suite with C and C++ plugins"() {
+        given:
+        useConventionalSourceLocations()
+        useStandardConfig()
+        buildFile << "apply plugin: 'c'"
+        file("src/hello/c").createDir().file("foo.c").text = "int foobar() { return 0; }"
+
+        when:
+        run "runHelloTestGoogleTestExe"
+
+        then:
+        executedAndNotSkipped ":compileHelloTestGoogleTestExeHelloCpp", ":compileHelloTestGoogleTestExeHelloC",
+            ":compileHelloTestGoogleTestExeHelloTestCpp",
+            ":linkHelloTestGoogleTestExe", ":helloTestGoogleTestExe", ":runHelloTestGoogleTestExe"
+    }
+
     def "can configure via testSuite component"() {
         given:
         useConventionalSourceLocations()
@@ -150,6 +179,7 @@ tasks.withType(RunTestExecutable) {
     args "--gtest_output=xml:test_detail.xml"
 }
 """
+        addGoogleTestDep()
 
         when:
         run "runHelloTestGoogleTestExe"
@@ -196,8 +226,7 @@ model {
     testSuites {
         helloTest {
             sources {
-                // TODO:DAZ Should not need type here (source set should already be created)
-                cpp(CppSourceSet) {
+                cpp {
                     source.srcDir "src/alternateHelloTest/cpp"
                 }
             }
@@ -221,8 +250,7 @@ model {
     testSuites {
         helloTest {
             sources {
-                // TODO:DAZ Should not need type here (source set should already be created)
-                cpp(CppSourceSet) {
+                cpp {
                     source.srcDir "src/alternateHelloTest/cpp"
                 }
             }
@@ -258,10 +286,8 @@ model {
         }
     }
 }
-binaries.withType(GoogleTestTestSuiteBinarySpec) {
-    lib library: "googleTest", linkage: "static"
-}
 """
+        addGoogleTestDep()
 
         then:
         succeeds "runHelloTestGoogleTestExe"
@@ -384,6 +410,7 @@ tasks.withType(RunTestExecutable) {
                 "src/hello/cpp/sum.cpp"
         ] as Set
         projectFile.headerFiles == [
+                "src/hello/headers/common.h",
                 "src/hello/headers/hello.h"
         ]
         projectFile.projectConfigurations.keySet() == ['debug'] as Set
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteBinary.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteBinary.java
index 383bdd6..e514ea5 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteBinary.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteBinary.java
@@ -16,28 +16,11 @@
 
 package org.gradle.nativeplatform.test.cunit.internal;
 
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.nativeplatform.NativeComponentSpec;
-import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
-import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
 import org.gradle.nativeplatform.test.cunit.CUnitTestSuiteBinarySpec;
 import org.gradle.nativeplatform.test.cunit.CUnitTestSuiteSpec;
 import org.gradle.nativeplatform.test.internal.DefaultNativeTestSuiteBinarySpec;
-import org.gradle.platform.base.binary.BaseBinarySpec;
-import org.gradle.platform.base.internal.BinaryNamingScheme;
 
 public class DefaultCUnitTestSuiteBinary extends DefaultNativeTestSuiteBinarySpec implements CUnitTestSuiteBinarySpec {
-
-    public static DefaultCUnitTestSuiteBinary create(NativeComponentSpec owner, NativeBinarySpecInternal testedBinary, BinaryNamingScheme namingScheme, NativeDependencyResolver resolver, Instantiator instantiator, ITaskFactory taskFactory) {
-        DefaultCUnitTestSuiteBinary spec = BaseBinarySpec.create(DefaultCUnitTestSuiteBinary.class, namingScheme.getLifecycleTaskName(), instantiator, taskFactory);
-        spec.setComponent(owner);
-        spec.setTestedBinary(testedBinary);
-        spec.setNamingScheme(namingScheme);
-        spec.setResolver(resolver);
-        return spec;
-    }
-
     @Override
     public CUnitTestSuiteSpec getTestSuite() {
         return getComponent();
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteSpec.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteSpec.java
index 52f3ba7..fbd351e 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteSpec.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/internal/DefaultCUnitTestSuiteSpec.java
@@ -18,9 +18,8 @@ package org.gradle.nativeplatform.test.cunit.internal;
 import org.gradle.nativeplatform.NativeComponentSpec;
 import org.gradle.nativeplatform.internal.AbstractNativeComponentSpec;
 import org.gradle.nativeplatform.test.cunit.CUnitTestSuiteSpec;
-import org.gradle.platform.base.internal.ComponentSpecInternal;
 
-public class DefaultCUnitTestSuiteSpec extends AbstractNativeComponentSpec implements CUnitTestSuiteSpec, ComponentSpecInternal {
+public class DefaultCUnitTestSuiteSpec extends AbstractNativeComponentSpec implements CUnitTestSuiteSpec {
     private NativeComponentSpec testedComponent;
 
     public String getDisplayName() {
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/plugins/CUnitPlugin.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/plugins/CUnitPlugin.java
index 1b850e7..9088044 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/plugins/CUnitPlugin.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cunit/plugins/CUnitPlugin.java
@@ -16,41 +16,38 @@
 
 package org.gradle.nativeplatform.test.cunit.plugins;
 
-import org.gradle.api.*;
-import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskContainer;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.ProjectSourceSet;
-import org.gradle.language.base.internal.DefaultFunctionalSourceSet;
-import org.gradle.language.base.sources.BaseLanguageSourceSet;
+import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.c.CSourceSet;
-import org.gradle.language.c.internal.DefaultCSourceSet;
 import org.gradle.language.c.plugins.CLangPlugin;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.model.*;
 import org.gradle.nativeplatform.NativeBinarySpec;
 import org.gradle.nativeplatform.NativeComponentSpec;
 import org.gradle.nativeplatform.SharedLibraryBinary;
 import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
+import org.gradle.nativeplatform.internal.configure.ToolSettingNativeBinaryInitializer;
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativeplatform.test.cunit.CUnitTestSuiteBinarySpec;
 import org.gradle.nativeplatform.test.cunit.CUnitTestSuiteSpec;
 import org.gradle.nativeplatform.test.cunit.internal.DefaultCUnitTestSuiteBinary;
 import org.gradle.nativeplatform.test.cunit.internal.DefaultCUnitTestSuiteSpec;
 import org.gradle.nativeplatform.test.cunit.tasks.GenerateCUnitLauncher;
 import org.gradle.nativeplatform.test.plugins.NativeBinariesTestPlugin;
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.ComponentSpecIdentifier;
-import org.gradle.platform.base.component.BaseComponentSpec;
+import org.gradle.platform.base.BinaryType;
+import org.gradle.platform.base.BinaryTypeBuilder;
+import org.gradle.platform.base.ComponentType;
+import org.gradle.platform.base.ComponentTypeBuilder;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
 import org.gradle.platform.base.internal.ComponentSpecInternal;
 import org.gradle.platform.base.internal.DefaultBinaryNamingSchemeBuilder;
-import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier;
 import org.gradle.platform.base.test.TestSuiteContainer;
 
 import java.io.File;
@@ -73,38 +70,28 @@ public class CUnitPlugin implements Plugin<Project> {
 
         // TODO:DAZ Test suites should belong to ComponentSpecContainer, and we could rely on more conventions from the base plugins
         @Defaults
-        public void createCUnitTestSuitePerComponent(TestSuiteContainer testSuites, NamedDomainObjectSet<NativeComponentSpec> components, ProjectSourceSet projectSourceSet, ServiceRegistry serviceRegistry) {
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
-            for (NativeComponentSpec component : components) {
-                testSuites.add(createCUnitTestSuite(component, instantiator, projectSourceSet, fileResolver));
+        public void createCUnitTestSuitePerComponent(TestSuiteContainer testSuites, ModelMap<NativeComponentSpec> components) {
+            for (final NativeComponentSpec component : components.values()) {
+                final String suiteName = String.format("%sTest", component.getName());
+                testSuites.create(suiteName, CUnitTestSuiteSpec.class, new Action<CUnitTestSuiteSpec>() {
+                    @Override
+                    public void execute(CUnitTestSuiteSpec testSuite) {
+                        DefaultCUnitTestSuiteSpec cunitTestSuite = (DefaultCUnitTestSuiteSpec) testSuite;
+                        cunitTestSuite.setTestedComponent(component);
+                    }
+                });
             }
         }
 
-        private CUnitTestSuiteSpec createCUnitTestSuite(final NativeComponentSpec testedComponent, Instantiator instantiator, ProjectSourceSet projectSourceSet, FileResolver fileResolver) {
-            String suiteName = String.format("%sTest", testedComponent.getName());
-            String path = testedComponent.getProjectPath();
-            ComponentSpecIdentifier id = new DefaultComponentSpecIdentifier(path, suiteName);
-            FunctionalSourceSet testSuiteSourceSet = createCUnitSources(instantiator, suiteName, projectSourceSet, fileResolver);
-            CUnitTestSuiteSpec testSuiteSpec = BaseComponentSpec.create(DefaultCUnitTestSuiteSpec.class, id, testSuiteSourceSet, instantiator);
-            testSuiteSpec.setTestedComponent(testedComponent);
-            return testSuiteSpec;
-        }
-
-        private FunctionalSourceSet createCUnitSources(final Instantiator instantiator, final String suiteName, ProjectSourceSet projectSourceSet, final FileResolver fileResolver) {
-            final FunctionalSourceSet functionalSourceSet = instantiator.newInstance(DefaultFunctionalSourceSet.class, suiteName, instantiator, projectSourceSet);
-            functionalSourceSet.registerFactory(CSourceSet.class, new NamedDomainObjectFactory<CSourceSet>() {
-                public CSourceSet create(String name) {
-                    return BaseLanguageSourceSet.create(DefaultCSourceSet.class, name, suiteName, fileResolver, instantiator);
-                }
-            });
-            return functionalSourceSet;
+        @ComponentType
+        public  void registerCUnitTestSuiteSpecType(ComponentTypeBuilder<CUnitTestSuiteSpec> builder) {
+            builder.defaultImplementation(DefaultCUnitTestSuiteSpec.class);
         }
 
         @Finalize
         public void configureCUnitTestSuiteSources(TestSuiteContainer testSuites, @Path("buildDir") File buildDir) {
 
-            for (final CUnitTestSuiteSpec suite : testSuites.withType(CUnitTestSuiteSpec.class)) {
+            for (final CUnitTestSuiteSpec suite : testSuites.withType(CUnitTestSuiteSpec.class).values()) {
                 FunctionalSourceSet suiteSourceSet = ((ComponentSpecInternal) suite).getSources();
                 CSourceSet launcherSources = suiteSourceSet.maybeCreate(CUNIT_LAUNCHER_SOURCE_SET, CSourceSet.class);
                 File baseDir = new File(buildDir, String.format("src/%s/cunitLauncher", suite.getName()));
@@ -112,63 +99,67 @@ public class CUnitPlugin implements Plugin<Project> {
                 launcherSources.getExportedHeaders().srcDir(new File(baseDir, "headers"));
 
                 CSourceSet testSources = suiteSourceSet.maybeCreate("c", CSourceSet.class);
-                testSources.getSource().srcDir(String.format("src/%s/%s", suite.getName(), "c"));
-                testSources.getExportedHeaders().srcDir(String.format("src/%s/headers", suite.getName()));
-
                 testSources.lib(launcherSources);
             }
         }
 
         @Mutate
         public void createCUnitLauncherTasks(TaskContainer tasks, TestSuiteContainer testSuites) {
-            for (final CUnitTestSuiteSpec suite : testSuites.withType(CUnitTestSuiteSpec.class)) {
+            for (final CUnitTestSuiteSpec suite : testSuites.withType(CUnitTestSuiteSpec.class).values()) {
 
                 String taskName = suite.getName() + "CUnitLauncher";
                 GenerateCUnitLauncher skeletonTask = tasks.create(taskName, GenerateCUnitLauncher.class);
 
-                CSourceSet launcherSources = findLaucherSources(suite);
+                CSourceSet launcherSources = findLauncherSources(suite);
                 skeletonTask.setSourceDir(launcherSources.getSource().getSrcDirs().iterator().next());
                 skeletonTask.setHeaderDir(launcherSources.getExportedHeaders().getSrcDirs().iterator().next());
                 launcherSources.builtBy(skeletonTask);
             }
         }
 
-        private CSourceSet findLaucherSources(CUnitTestSuiteSpec suite) {
-            return suite.getSource().withType(CSourceSet.class).matching(new Spec<CSourceSet>() {
-                public boolean isSatisfiedBy(CSourceSet element) {
-                    return element.getName().equals(CUNIT_LAUNCHER_SOURCE_SET);
-                }
-            }).iterator().next();
+        private CSourceSet findLauncherSources(CUnitTestSuiteSpec suite) {
+            return suite.getSource().withType(CSourceSet.class).get(CUNIT_LAUNCHER_SOURCE_SET);
         }
 
-        @Mutate
-        public void createCUnitTestBinaries(final BinaryContainer binaries, TestSuiteContainer testSuites, @Path("buildDir") File buildDir, ServiceRegistry serviceRegistry, ITaskFactory taskFactory) {
-            for (final CUnitTestSuiteSpec cUnitTestSuite : testSuites.withType(CUnitTestSuiteSpec.class)) {
-                for (NativeBinarySpec testedBinary : cUnitTestSuite.getTestedComponent().getBinaries().withType(NativeBinarySpec.class)) {
+        @BinaryType
+        public void registerCUnitTestBinaryType(BinaryTypeBuilder<CUnitTestSuiteBinarySpec> builder) {
+            builder.defaultImplementation(DefaultCUnitTestSuiteBinary.class);
+        }
 
-                    if (testedBinary instanceof SharedLibraryBinary) {
-                        // TODO:DAZ For now, we only create test suites for static library variants
-                        continue;
+        @Mutate
+        public void createCUnitTestBinaries(TestSuiteContainer testSuites, @Path("buildDir") final File buildDir,
+                                            LanguageTransformContainer languageTransforms, final ServiceRegistry serviceRegistry, final ITaskFactory taskFactory) {
+            final Action<NativeBinarySpec> setToolsAction = new ToolSettingNativeBinaryInitializer(languageTransforms);
+
+            testSuites.withType(CUnitTestSuiteSpec.class).afterEach(new Action<CUnitTestSuiteSpec>() {
+                @Override
+                public void execute(final CUnitTestSuiteSpec cUnitTestSuite) {
+                    for (final NativeBinarySpec testedBinary : cUnitTestSuite.getTestedComponent().getBinaries().withType(NativeBinarySpec.class).values()) {
+
+                        if (testedBinary instanceof SharedLibraryBinary) {
+                            // TODO:DAZ For now, we only create test suites for static library variants
+                            continue;
+                        }
+
+                        final BinaryNamingScheme namingScheme = new DefaultBinaryNamingSchemeBuilder(((NativeBinarySpecInternal) testedBinary).getNamingScheme())
+                            .withComponentName(cUnitTestSuite.getBaseName())
+                            .withTypeString("CUnitExe").build();
+                        final NativeDependencyResolver resolver = serviceRegistry.get(NativeDependencyResolver.class);
+
+                        cUnitTestSuite.getBinaries().create(namingScheme.getLifecycleTaskName(), CUnitTestSuiteBinarySpec.class, new Action<CUnitTestSuiteBinarySpec>() {
+                            @Override
+                            public void execute(CUnitTestSuiteBinarySpec binary) {
+                                DefaultCUnitTestSuiteBinary testBinary = (DefaultCUnitTestSuiteBinary) binary;
+                                testBinary.setTestedBinary((NativeBinarySpecInternal) testedBinary);
+                                testBinary.setNamingScheme(namingScheme);
+                                testBinary.setResolver(resolver);
+                                setToolsAction.execute(testBinary);
+                                configure(testBinary, buildDir);
+                            }
+                        });
                     }
-
-                    DefaultCUnitTestSuiteBinary testBinary = createTestBinary(serviceRegistry, cUnitTestSuite, testedBinary, taskFactory);
-
-                    configure(testBinary, buildDir);
-
-                    cUnitTestSuite.getBinaries().add(testBinary);
-                    binaries.add(testBinary);
                 }
-            }
-        }
-
-        private DefaultCUnitTestSuiteBinary createTestBinary(ServiceRegistry serviceRegistry, CUnitTestSuiteSpec cUnitTestSuite, NativeBinarySpec testedBinary, ITaskFactory taskFactory) {
-            BinaryNamingScheme namingScheme = new DefaultBinaryNamingSchemeBuilder(((NativeBinarySpecInternal) testedBinary).getNamingScheme())
-                    .withComponentName(cUnitTestSuite.getBaseName())
-                    .withTypeString("CUnitExe").build();
-
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            NativeDependencyResolver resolver = serviceRegistry.get(NativeDependencyResolver.class);
-            return DefaultCUnitTestSuiteBinary.create(cUnitTestSuite, (NativeBinarySpecInternal) testedBinary, namingScheme, resolver, instantiator, taskFactory);
+            });
         }
 
         private void configure(DefaultCUnitTestSuiteBinary testBinary, File buildDir) {
@@ -178,8 +169,6 @@ public class CUnitPlugin implements Plugin<Project> {
             String baseName = testBinary.getComponent().getBaseName();
 
             testBinary.setExecutableFile(new File(binaryOutputDir, toolProvider.getExecutableName(baseName)));
-
-            ((ExtensionAware) testBinary).getExtensions().create("cCompiler", DefaultPreprocessingTool.class);
         }
     }
 }
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteBinary.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteBinary.java
index f32d955..d13f1ff 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteBinary.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteBinary.java
@@ -16,28 +16,11 @@
 
 package org.gradle.nativeplatform.test.googletest.internal;
 
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.nativeplatform.NativeComponentSpec;
-import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
-import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
 import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
 import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteSpec;
 import org.gradle.nativeplatform.test.internal.DefaultNativeTestSuiteBinarySpec;
-import org.gradle.platform.base.binary.BaseBinarySpec;
-import org.gradle.platform.base.internal.BinaryNamingScheme;
 
 public class DefaultGoogleTestTestSuiteBinary extends DefaultNativeTestSuiteBinarySpec implements GoogleTestTestSuiteBinarySpec {
-
-    public static DefaultGoogleTestTestSuiteBinary create(NativeComponentSpec owner, NativeBinarySpecInternal testedBinary, BinaryNamingScheme namingScheme, NativeDependencyResolver resolver, Instantiator instantiator, ITaskFactory taskFactory) {
-        DefaultGoogleTestTestSuiteBinary spec = BaseBinarySpec.create(DefaultGoogleTestTestSuiteBinary.class, namingScheme.getLifecycleTaskName(), instantiator, taskFactory);
-        spec.setComponent(owner);
-        spec.setTestedBinary(testedBinary);
-        spec.setNamingScheme(namingScheme);
-        spec.setResolver(resolver);
-        return spec;
-    }
-
     @Override
     public GoogleTestTestSuiteSpec getTestSuite() {
         return getComponent();
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteSpec.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteSpec.java
index 1d0cdd8..84145cc 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteSpec.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/internal/DefaultGoogleTestTestSuiteSpec.java
@@ -18,11 +18,11 @@ package org.gradle.nativeplatform.test.googletest.internal;
 import org.gradle.nativeplatform.NativeComponentSpec;
 import org.gradle.nativeplatform.internal.AbstractNativeComponentSpec;
 import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteSpec;
-import org.gradle.platform.base.internal.ComponentSpecInternal;
 
-public class DefaultGoogleTestTestSuiteSpec extends AbstractNativeComponentSpec implements GoogleTestTestSuiteSpec, ComponentSpecInternal {
+public class DefaultGoogleTestTestSuiteSpec extends AbstractNativeComponentSpec implements GoogleTestTestSuiteSpec {
     private NativeComponentSpec testedComponent;
 
+
     public String getDisplayName() {
         return String.format("googleTest test suite '%s'", getName());
     }
diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/plugins/GoogleTestPlugin.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/plugins/GoogleTestPlugin.java
index 7d881e5..290e312 100644
--- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/plugins/GoogleTestPlugin.java
+++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/googletest/plugins/GoogleTestPlugin.java
@@ -16,43 +16,39 @@
 
 package org.gradle.nativeplatform.test.googletest.plugins;
 
-import org.gradle.api.*;
-import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.language.base.FunctionalSourceSet;
-import org.gradle.language.base.ProjectSourceSet;
-import org.gradle.language.base.internal.DefaultFunctionalSourceSet;
-import org.gradle.language.base.sources.BaseLanguageSourceSet;
+import org.gradle.language.base.internal.registry.LanguageTransformContainer;
 import org.gradle.language.cpp.CppSourceSet;
-import org.gradle.language.cpp.internal.DefaultCppSourceSet;
 import org.gradle.language.cpp.plugins.CppLangPlugin;
-import org.gradle.language.nativeplatform.internal.DefaultPreprocessingTool;
 import org.gradle.model.*;
 import org.gradle.nativeplatform.NativeBinarySpec;
 import org.gradle.nativeplatform.NativeComponentSpec;
 import org.gradle.nativeplatform.SharedLibraryBinary;
 import org.gradle.nativeplatform.internal.NativeBinarySpecInternal;
+import org.gradle.nativeplatform.internal.configure.ToolSettingNativeBinaryInitializer;
 import org.gradle.nativeplatform.internal.resolve.NativeDependencyResolver;
+import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
 import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteSpec;
 import org.gradle.nativeplatform.test.googletest.internal.DefaultGoogleTestTestSuiteBinary;
 import org.gradle.nativeplatform.test.googletest.internal.DefaultGoogleTestTestSuiteSpec;
 import org.gradle.nativeplatform.test.plugins.NativeBinariesTestPlugin;
-import org.gradle.nativeplatform.toolchain.GccCompatibleToolChain;
 import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
-import org.gradle.platform.base.BinaryContainer;
-import org.gradle.platform.base.ComponentSpecIdentifier;
-import org.gradle.platform.base.component.BaseComponentSpec;
+import org.gradle.platform.base.BinaryType;
+import org.gradle.platform.base.BinaryTypeBuilder;
+import org.gradle.platform.base.ComponentType;
+import org.gradle.platform.base.ComponentTypeBuilder;
 import org.gradle.platform.base.internal.BinaryNamingScheme;
 import org.gradle.platform.base.internal.ComponentSpecInternal;
 import org.gradle.platform.base.internal.DefaultBinaryNamingSchemeBuilder;
-import org.gradle.platform.base.internal.DefaultComponentSpecIdentifier;
 import org.gradle.platform.base.test.TestSuiteContainer;
 
 import java.io.File;
-import java.util.Collections;
 
 /**
  * A plugin that sets up the infrastructure for testing native binaries with GoogleTest.
@@ -61,81 +57,78 @@ import java.util.Collections;
 public class GoogleTestPlugin implements Plugin<Project> {
 
     public void apply(final Project project) {
-        project.apply(Collections.singletonMap("plugin", NativeBinariesTestPlugin.class));
-        project.apply(Collections.singletonMap("plugin", CppLangPlugin.class));
+        project.getPluginManager().apply(NativeBinariesTestPlugin.class);
+        project.getPluginManager().apply(CppLangPlugin.class);
     }
 
     @SuppressWarnings("UnusedDeclaration")
     static class Rules extends RuleSource {
-
-        // TODO:DAZ Test suites should belong to ComponentSpecContainer, and we could rely on more conventions from the base plugins
         @Defaults
-        public void createGoogleTestTestSuitePerComponent(TestSuiteContainer testSuites, NamedDomainObjectSet<NativeComponentSpec> components, ProjectSourceSet projectSourceSet, ServiceRegistry serviceRegistry) {
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            FileResolver fileResolver = serviceRegistry.get(FileResolver.class);
-            for (NativeComponentSpec component : components) {
-                testSuites.add(createGoogleTestTestSuite(component, instantiator, projectSourceSet, fileResolver));
+        public void createGoogleTestTestSuitePerComponent(TestSuiteContainer testSuites, ModelMap<NativeComponentSpec> components) {
+            for (final NativeComponentSpec component : components.values()) {
+                final String suiteName = String.format("%sTest", component.getName());
+                testSuites.create(suiteName, GoogleTestTestSuiteSpec.class, new Action<GoogleTestTestSuiteSpec>() {
+                    @Override
+                    public void execute(GoogleTestTestSuiteSpec testSuite) {
+                        DefaultGoogleTestTestSuiteSpec googleTestSuite = (DefaultGoogleTestTestSuiteSpec) testSuite;
+                        googleTestSuite.setTestedComponent(component);
+                    }
+                });
             }
         }
 
-        private GoogleTestTestSuiteSpec createGoogleTestTestSuite(final NativeComponentSpec testedComponent, Instantiator instantiator, ProjectSourceSet projectSourceSet, FileResolver fileResolver) {
-            String suiteName = String.format("%sTest", testedComponent.getName());
-            String path = testedComponent.getProjectPath();
-            ComponentSpecIdentifier id = new DefaultComponentSpecIdentifier(path, suiteName);
-            FunctionalSourceSet testSuiteSourceSet = createGoogleTestSources(instantiator, suiteName, projectSourceSet, fileResolver);
-            GoogleTestTestSuiteSpec testSuiteSpec = BaseComponentSpec.create(DefaultGoogleTestTestSuiteSpec.class, id, testSuiteSourceSet, instantiator);
-            testSuiteSpec.setTestedComponent(testedComponent);
-            return testSuiteSpec;
-        }
-
-        private FunctionalSourceSet createGoogleTestSources(final Instantiator instantiator, final String suiteName, ProjectSourceSet projectSourceSet, final FileResolver fileResolver) {
-            final FunctionalSourceSet functionalSourceSet = instantiator.newInstance(DefaultFunctionalSourceSet.class, suiteName, instantiator, projectSourceSet);
-            functionalSourceSet.registerFactory(CppSourceSet.class, new NamedDomainObjectFactory<CppSourceSet>() {
-                public CppSourceSet create(String name) {
-                    return BaseLanguageSourceSet.create(DefaultCppSourceSet.class, name, suiteName, fileResolver, instantiator);
-                }
-            });
-            return functionalSourceSet;
+        @ComponentType
+        public void registerGoogleTestSuiteSpecTest(ComponentTypeBuilder<GoogleTestTestSuiteSpec> builder) {
+            builder.defaultImplementation(DefaultGoogleTestTestSuiteSpec.class);
         }
 
         @Finalize
         public void configureGoogleTestTestSuiteSources(TestSuiteContainer testSuites, @Path("buildDir") File buildDir) {
 
-            for (final GoogleTestTestSuiteSpec suite : testSuites.withType(GoogleTestTestSuiteSpec.class)) {
+            for (final GoogleTestTestSuiteSpec suite : testSuites.withType(GoogleTestTestSuiteSpec.class).values()) {
                 FunctionalSourceSet suiteSourceSet = ((ComponentSpecInternal) suite).getSources();
-
-                CppSourceSet testSources = suiteSourceSet.maybeCreate("cpp", CppSourceSet.class);
-                testSources.getSource().srcDir(String.format("src/%s/%s", suite.getName(), "cpp"));
-                testSources.getExportedHeaders().srcDir(String.format("src/%s/headers", suite.getName()));
+                suiteSourceSet.maybeCreate("cpp", CppSourceSet.class);
             }
         }
 
+        @BinaryType
+        public void registerGoogleTestSuiteBinaryType(BinaryTypeBuilder<GoogleTestTestSuiteBinarySpec> builder) {
+            builder.defaultImplementation(DefaultGoogleTestTestSuiteBinary.class);
+        }
+
         @Mutate
-        public void createGoogleTestTestBinaries(final BinaryContainer binaries, TestSuiteContainer testSuites, @Path("buildDir") File buildDir, ServiceRegistry serviceRegistry, ITaskFactory taskFactory) {
-            for (final GoogleTestTestSuiteSpec googleTestTestSuite : testSuites.withType(GoogleTestTestSuiteSpec.class)) {
-                for (NativeBinarySpec testedBinary : googleTestTestSuite.getTestedComponent().getBinaries().withType(NativeBinarySpec.class)) {
-                    if (testedBinary instanceof SharedLibraryBinary) {
-                        // TODO:DAZ For now, we only create test suites for static library variants
-                        continue;
+        public void createGoogleTestTestBinaries(TestSuiteContainer testSuites, @Path("buildDir") final File buildDir,
+                                                 LanguageTransformContainer languageTransforms, final ServiceRegistry serviceRegistry, final ITaskFactory taskFactory) {
+            final Action<NativeBinarySpec> setToolsAction = new ToolSettingNativeBinaryInitializer(languageTransforms);
+
+            testSuites.withType(GoogleTestTestSuiteSpec.class).afterEach(new Action<GoogleTestTestSuiteSpec>() {
+                @Override
+                public void execute(final GoogleTestTestSuiteSpec testSuiteSpec) {
+                    for (final NativeBinarySpec testedBinary : testSuiteSpec.getTestedComponent().getBinaries().withType(NativeBinarySpec.class).values()) {
+                        if (testedBinary instanceof SharedLibraryBinary) {
+                            // TODO:DAZ For now, we only create test suites for static library variants
+                            continue;
+                        }
+
+                        final BinaryNamingScheme namingScheme = new DefaultBinaryNamingSchemeBuilder(((NativeBinarySpecInternal) testedBinary).getNamingScheme())
+                            .withComponentName(testSuiteSpec.getBaseName())
+                            .withTypeString("GoogleTestExe").build();
+                        final NativeDependencyResolver resolver = serviceRegistry.get(NativeDependencyResolver.class);
+
+                        testSuiteSpec.getBinaries().create(namingScheme.getLifecycleTaskName(), GoogleTestTestSuiteBinarySpec.class, new Action<GoogleTestTestSuiteBinarySpec>() {
+                            @Override
+                            public void execute(GoogleTestTestSuiteBinarySpec binary) {
+                                DefaultGoogleTestTestSuiteBinary testBinary = (DefaultGoogleTestTestSuiteBinary) binary;
+                                testBinary.setTestedBinary((NativeBinarySpecInternal) testedBinary);
+                                testBinary.setNamingScheme(namingScheme);
+                                testBinary.setResolver(resolver);
+                                setToolsAction.execute(testBinary);
+                                configure(testBinary, buildDir);
+                            }
+                        });
                     }
-                    DefaultGoogleTestTestSuiteBinary testBinary = createTestBinary(serviceRegistry, googleTestTestSuite, testedBinary, taskFactory);
-
-                    configure(testBinary, buildDir);
-
-                    googleTestTestSuite.getBinaries().add(testBinary);
-                    binaries.add(testBinary);
                 }
-            }
-        }
-
-        private DefaultGoogleTestTestSuiteBinary createTestBinary(ServiceRegistry serviceRegistry, GoogleTestTestSuiteSpec googleTestTestSuite, NativeBinarySpec testedBinary, ITaskFactory taskFactory) {
-            BinaryNamingScheme namingScheme = new DefaultBinaryNamingSchemeBuilder(((NativeBinarySpecInternal) testedBinary).getNamingScheme())
-                    .withComponentName(googleTestTestSuite.getBaseName())
-                    .withTypeString("GoogleTestExe").build();
-
-            Instantiator instantiator = serviceRegistry.get(Instantiator.class);
-            NativeDependencyResolver resolver = serviceRegistry.get(NativeDependencyResolver.class);
-            return DefaultGoogleTestTestSuiteBinary.create(googleTestTestSuite, (NativeBinarySpecInternal) testedBinary, namingScheme, resolver, instantiator, taskFactory);
+            });
         }
 
         private void configure(DefaultGoogleTestTestSuiteBinary testBinary, File buildDir) {
@@ -145,15 +138,6 @@ public class GoogleTestPlugin implements Plugin<Project> {
             String baseName = testBinary.getComponent().getBaseName();
 
             testBinary.setExecutableFile(new File(binaryOutputDir, toolProvider.getExecutableName(baseName)));
-
-            ((ExtensionAware) testBinary).getExtensions().create("cppCompiler", DefaultPreprocessingTool.class);
-
-            // TODO:DAZ Not sure if this should be here...
-            // Need "-pthread" when linking on Linux
-            if (testBinary.getToolChain() instanceof GccCompatibleToolChain
-                    && testBinary.getTargetPlatform().getOperatingSystem().isLinux()) {
-                testBinary.getLinker().args("-pthread");
-            }
         }
     }
 }
diff --git a/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/cunit/CUnitTest.groovy b/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/cunit/CUnitTest.groovy
index 0ea8fb4..791a679 100644
--- a/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/cunit/CUnitTest.groovy
+++ b/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/cunit/CUnitTest.groovy
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 package org.gradle.nativeplatform.test.cunit
-import org.gradle.language.c.plugins.CPlugin
+
+import org.gradle.language.c.CSourceSet
 import org.gradle.model.internal.core.ModelPath
-import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.NativeLibrarySpec
-import org.gradle.platform.base.test.TestSuiteContainer
 import org.gradle.nativeplatform.test.cunit.plugins.CUnitPlugin
+import org.gradle.platform.base.test.TestSuiteSpec
 import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class CUnitTest extends Specification {
     final def project = TestUtil.createRootProject();
 
-    def "check the correct binary type are created for the test suite"() {
-        when:
-        project.pluginManager.apply(CPlugin)
+    def "creates a test suite for each library under test"() {
+        given:
         project.pluginManager.apply(CUnitPlugin)
         project.model {
             components {
@@ -37,8 +37,17 @@ class CUnitTest extends Specification {
         }
         project.evaluate()
 
+        when:
+        CUnitTestSuiteSpec testSuite = project.modelRegistry.realize(ModelPath.path("testSuites"), ModelTypes.modelMap(TestSuiteSpec)).mainTest
+        def sources = testSuite.source.values()
+        def binaries = testSuite.binaries.values()
+
         then:
-        def binaries = project.modelRegistry.realize(ModelPath.path("testSuites"), ModelType.of(TestSuiteContainer)).getByName("mainTest").binaries
-        binaries.collect({ it instanceof CUnitTestSuiteBinarySpec }) == [true] * binaries.size()
+        sources.size() == 2
+        sources.every { it instanceof CSourceSet }
+
+        and:
+        binaries.size() == 1
+        binaries.every { it instanceof CUnitTestSuiteBinarySpec }
     }
 }
diff --git a/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestTest.groovy b/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestTest.groovy
index e571fb4..de4da32 100644
--- a/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestTest.groovy
+++ b/subprojects/testing-native/src/test/groovy/org/gradle/nativeplatform/test/googletest/GoogleTestTest.groovy
@@ -14,20 +14,22 @@
  * limitations under the License.
  */
 package org.gradle.nativeplatform.test.googletest
+
+import org.gradle.language.cpp.CppSourceSet
 import org.gradle.language.cpp.plugins.CppPlugin
 import org.gradle.model.internal.core.ModelPath
-import org.gradle.model.internal.type.ModelType
+import org.gradle.model.internal.type.ModelTypes
 import org.gradle.nativeplatform.NativeLibrarySpec
 import org.gradle.nativeplatform.test.googletest.plugins.GoogleTestPlugin
-import org.gradle.platform.base.test.TestSuiteContainer
+import org.gradle.platform.base.test.TestSuiteSpec
 import org.gradle.util.TestUtil
 import spock.lang.Specification
 
 class GoogleTestTest extends Specification {
     final def project = TestUtil.createRootProject();
 
-    def "check the correct binary type are created for the test suite"() {
-        when:
+    def "creates a test suite for each library under test"() {
+        given:
         project.apply(plugin: CppPlugin)
         project.apply(plugin: GoogleTestPlugin)
         project.model {
@@ -37,8 +39,17 @@ class GoogleTestTest extends Specification {
         }
         project.evaluate()
 
+        when:
+        GoogleTestTestSuiteSpec testSuite = project.modelRegistry.realize(ModelPath.path("testSuites"), ModelTypes.modelMap(TestSuiteSpec)).mainTest
+        def sources = testSuite.source.values()
+        def binaries = testSuite.binaries.values()
+
         then:
-        def binaries = project.modelRegistry.realize(ModelPath.path("testSuites"), ModelType.of(TestSuiteContainer)).getByName("mainTest").binaries
-        binaries.collect({ it instanceof GoogleTestTestSuiteBinarySpec }) == [true] * binaries.size()
+        sources.size() == 1
+        sources.every { it instanceof CppSourceSet }
+
+        and:
+        binaries.size() == 1
+        binaries.every { it instanceof GoogleTestTestSuiteBinarySpec }
     }
 }
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildClientSubscriptionsSetup.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildClientSubscriptionsSetup.java
new file mode 100644
index 0000000..5d6e764
--- /dev/null
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildClientSubscriptionsSetup.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.initialization.BuildEventConsumer;
+import org.gradle.tooling.internal.provider.BuildClientSubscriptions;
+
+class BuildClientSubscriptionsSetup {
+    /**
+     * Registers listeners with {@code Gradle} to receive those events for which the client has subscribed. Dispatch the events received from the Gradle listeners via {@code BuildEventConsumer}.
+     */
+    static void registerListenersForClientSubscriptions(BuildClientSubscriptions clientSubscriptions, GradleInternal gradle) {
+        BuildEventConsumer eventConsumer = gradle.getServices().get(BuildEventConsumer.class);
+        if (clientSubscriptions.isSendTestProgressEvents()) {
+            gradle.addListener(new ClientForwardingTestListener(eventConsumer, clientSubscriptions));
+        }
+        if (clientSubscriptions.isSendTaskProgressEvents()) {
+            gradle.addListener(new ClientForwardingTaskListener(eventConsumer, clientSubscriptions));
+        }
+        if (clientSubscriptions.isSendBuildProgressEvents()) {
+            gradle.addListener(new ClientForwardingBuildListener(eventConsumer));
+        }
+    }
+}
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
index 9b99f89..3231363 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/BuildModelActionRunner.java
@@ -20,7 +20,6 @@ import org.gradle.api.Project;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.execution.ProjectConfigurer;
-import org.gradle.initialization.BuildEventConsumer;
 import org.gradle.internal.invocation.BuildAction;
 import org.gradle.internal.invocation.BuildActionRunner;
 import org.gradle.internal.invocation.BuildController;
@@ -43,12 +42,9 @@ public class BuildModelActionRunner implements BuildActionRunner {
         BuildModelAction buildModelAction = (BuildModelAction) action;
         GradleInternal gradle = buildController.getGradle();
 
-        // register a TestListener that dispatches all test progress via the registered BuildEventConsumer instance,
-        // this allows to send test progress events back to the DaemonClient (via short-cut)
-        if (buildModelAction.isSendTestProgressEvents()) {
-            BuildEventConsumer eventConsumer = gradle.getServices().get(BuildEventConsumer.class);
-            gradle.addListener(new ClientForwardingTestListener(eventConsumer));
-        }
+        // register listeners that dispatch all progress via the registered BuildEventConsumer instance,
+        // this allows to send progress events back to the DaemonClient (via short-cut)
+        BuildClientSubscriptionsSetup.registerListenersForClientSubscriptions(buildModelAction.getClientSubscriptions(), gradle);
 
         if (buildModelAction.isRunTasks()) {
             buildController.run();
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingBuildListener.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingBuildListener.java
new file mode 100644
index 0000000..d1e518d
--- /dev/null
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingBuildListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner;
+
+import org.gradle.initialization.BuildEventConsumer;
+import org.gradle.internal.progress.BuildOperationInternal;
+import org.gradle.internal.progress.InternalBuildListener;
+import org.gradle.internal.progress.OperationResult;
+import org.gradle.internal.progress.OperationStartEvent;
+import org.gradle.tooling.internal.provider.events.*;
+
+import java.util.Collections;
+
+/**
+ * Build listener that forwards all receiving events to the client via the provided {@code BuildEventConsumer} instance.
+ *
+ * @since 2.5
+ */
+class ClientForwardingBuildListener implements InternalBuildListener {
+
+    private final BuildEventConsumer eventConsumer;
+
+    ClientForwardingBuildListener(BuildEventConsumer eventConsumer) {
+        this.eventConsumer = eventConsumer;
+    }
+
+    @Override
+    public void started(BuildOperationInternal buildOperation, OperationStartEvent startEvent) {
+        eventConsumer.dispatch(new DefaultOperationStartedProgressEvent(startEvent.getStartTime(), toBuildOperationDescriptor(buildOperation)));
+    }
+
+    @Override
+    public void finished(BuildOperationInternal buildOperation, OperationResult result) {
+        eventConsumer.dispatch(new DefaultOperationFinishedProgressEvent(result.getEndTime(), toBuildOperationDescriptor(buildOperation), adaptResult(result)));
+    }
+
+    private DefaultOperationDescriptor toBuildOperationDescriptor(BuildOperationInternal buildOperation) {
+        Object id = buildOperation.getId();
+        String name = buildOperation.getOperationType().getName();
+        String displayName = buildOperation.getOperationType().getDisplayName();
+        Object parentId = buildOperation.getParentId();
+        return new DefaultOperationDescriptor(id, name, displayName, parentId);
+    }
+
+    private AbstractOperationResult adaptResult(OperationResult result) {
+        Throwable failure = result.getFailure();
+        long startTime = result.getStartTime();
+        long endTime = result.getEndTime();
+        if (failure != null) {
+            return new DefaultFailureResult(startTime, endTime, Collections.singletonList(DefaultFailure.fromThrowable(failure)));
+        }
+        return new DefaultSuccessResult(startTime, endTime);
+    }
+}
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTaskListener.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTaskListener.java
new file mode 100644
index 0000000..76fa664
--- /dev/null
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTaskListener.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.runner;
+
+import org.gradle.api.execution.internal.InternalTaskExecutionListener;
+import org.gradle.api.execution.internal.TaskOperationInternal;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.initialization.BuildEventConsumer;
+import org.gradle.internal.progress.OperationResult;
+import org.gradle.internal.progress.OperationStartEvent;
+import org.gradle.tooling.internal.provider.BuildClientSubscriptions;
+import org.gradle.tooling.internal.provider.events.*;
+
+import java.util.Collections;
+
+/**
+ * Task listener that forwards all receiving events to the client via the provided {@code BuildEventConsumer} instance.
+ *
+ * @since 2.5
+ */
+class ClientForwardingTaskListener implements InternalTaskExecutionListener {
+
+    private final BuildEventConsumer eventConsumer;
+    private final BuildClientSubscriptions clientSubscriptions;
+
+    ClientForwardingTaskListener(BuildEventConsumer eventConsumer, BuildClientSubscriptions clientSubscriptions) {
+        this.eventConsumer = eventConsumer;
+        this.clientSubscriptions = clientSubscriptions;
+    }
+
+    @Override
+    public void beforeExecute(TaskOperationInternal taskOperation, OperationStartEvent startEvent) {
+        eventConsumer.dispatch(new DefaultTaskStartedProgressEvent(startEvent.getStartTime(), toTaskDescriptor(taskOperation)));
+    }
+
+    @Override
+    public void afterExecute(TaskOperationInternal taskOperation, OperationResult result) {
+        eventConsumer.dispatch(new DefaultTaskFinishedProgressEvent(result.getEndTime(), toTaskDescriptor(taskOperation), toTaskResult(taskOperation.getTask(), result)));
+    }
+
+    private DefaultTaskDescriptor toTaskDescriptor(TaskOperationInternal taskOperation) {
+        TaskInternal task = taskOperation.getTask();
+        Object id = taskOperation.getId();
+        String displayName = String.format("Task %s", task.getPath());
+        String taskPath = task.getPath();
+        Object parentId = getParentId(taskOperation);
+        return new DefaultTaskDescriptor(id, taskPath, displayName, parentId);
+    }
+
+    private Object getParentId(TaskOperationInternal taskOperation) {
+        // only set the BuildOperation as the parent if the Tooling API Consumer is listening to build progress events
+        return clientSubscriptions.isSendBuildProgressEvents() ? taskOperation.getParentId() : null;
+    }
+
+    private static AbstractTaskResult toTaskResult(TaskInternal task, OperationResult result) {
+        TaskStateInternal state = task.getState();
+        long startTime = result.getStartTime();
+        long endTime = result.getEndTime();
+
+        if (state.getUpToDate()) {
+            return new DefaultTaskSuccessResult(startTime, endTime, true);
+        } else if (state.getSkipped()) {
+            return new DefaultTaskSkippedResult(startTime, endTime, state.getSkipMessage());
+        } else {
+            Throwable failure = state.getFailure();
+            if (failure == null) {
+                return new DefaultTaskSuccessResult(startTime, endTime, false);
+            } else {
+                return new DefaultTaskFailureResult(startTime, endTime, Collections.singletonList(DefaultFailure.fromThrowable(failure)));
+            }
+        }
+    }
+
+}
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTestListener.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTestListener.java
index b715fe7..c65187c 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTestListener.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientForwardingTestListener.java
@@ -23,6 +23,7 @@ import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.api.tasks.testing.TestResult;
 import org.gradle.initialization.BuildEventConsumer;
 import org.gradle.tooling.internal.protocol.events.InternalJvmTestDescriptor;
+import org.gradle.tooling.internal.provider.BuildClientSubscriptions;
 import org.gradle.tooling.internal.provider.events.*;
 
 import java.util.ArrayList;
@@ -34,9 +35,11 @@ import java.util.List;
 class ClientForwardingTestListener implements TestListenerInternal {
 
     private final BuildEventConsumer eventConsumer;
+    private final BuildClientSubscriptions clientSubscriptions;
 
-    ClientForwardingTestListener(BuildEventConsumer eventConsumer) {
+    ClientForwardingTestListener(BuildEventConsumer eventConsumer, BuildClientSubscriptions clientSubscriptions) {
         this.eventConsumer = eventConsumer;
+        this.clientSubscriptions = clientSubscriptions;
     }
 
     @Override
@@ -54,11 +57,11 @@ class ClientForwardingTestListener implements TestListenerInternal {
         // Don't forward
     }
 
-    private static DefaultTestDescriptor adapt(TestDescriptorInternal testDescriptor) {
+    private DefaultTestDescriptor adapt(TestDescriptorInternal testDescriptor) {
         return testDescriptor.isComposite() ? toTestDescriptorForSuite(testDescriptor) : toTestDescriptorForTest(testDescriptor);
     }
 
-    private static DefaultTestDescriptor toTestDescriptorForSuite(TestDescriptorInternal suite) {
+    private DefaultTestDescriptor toTestDescriptorForSuite(TestDescriptorInternal suite) {
         Object id = suite.getId();
         String name = suite.getName();
         String displayName = suite.toString();
@@ -66,11 +69,11 @@ class ClientForwardingTestListener implements TestListenerInternal {
         String suiteName = suite.getName();
         String className = suite.getClassName();
         String methodName = null;
-        Object parentId = suite.getParent() != null ? suite.getParent().getId() : null;
+        Object parentId = getParentId(suite);
         return new DefaultTestDescriptor(id, name, displayName, testKind, suiteName, className, methodName, parentId);
     }
 
-    private static DefaultTestDescriptor toTestDescriptorForTest(TestDescriptorInternal test) {
+    private DefaultTestDescriptor toTestDescriptorForTest(TestDescriptorInternal test) {
         Object id = test.getId();
         String name = test.getName();
         String displayName = test.toString();
@@ -78,10 +81,22 @@ class ClientForwardingTestListener implements TestListenerInternal {
         String suiteName = null;
         String className = test.getClassName();
         String methodName = test.getName();
-        Object parentId = test.getParent() != null ? test.getParent().getId() : null;
+        Object parentId = getParentId(test);
         return new DefaultTestDescriptor(id, name, displayName, testKind, suiteName, className, methodName, parentId);
     }
 
+    private Object getParentId(TestDescriptorInternal descriptor) {
+        TestDescriptorInternal parent = descriptor.getParent();
+        if (parent != null) {
+            return parent.getId();
+        }
+        // only set the TaskOperation as the parent if the Tooling API Consumer is listening to task progress events
+        if (clientSubscriptions.isSendTaskProgressEvents()) {
+            return descriptor.getOwnerBuildOperationId();
+        }
+        return null;
+    }
+
     private static AbstractTestResult adapt(TestResult result) {
         TestResult.ResultType resultType = result.getResultType();
         switch (resultType) {
diff --git a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunner.java b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunner.java
index 572e0c2..64d5353 100644
--- a/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunner.java
+++ b/subprojects/tooling-api-builders/src/main/java/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunner.java
@@ -42,6 +42,10 @@ public class ClientProvidedBuildActionRunner implements BuildActionRunner {
         PayloadSerializer payloadSerializer = gradle.getServices().get(PayloadSerializer.class);
         InternalBuildAction<?> clientAction = (InternalBuildAction<?>) payloadSerializer.deserialize(clientProvidedBuildAction.getAction());
 
+        // register listeners that dispatch all progress via the registered BuildEventConsumer instance,
+        // this allows to send progress events back to the DaemonClient (via short-cut)
+        BuildClientSubscriptionsSetup.registerListenersForClientSubscriptions(clientProvidedBuildAction.getClientSubscriptions(), gradle);
+
         buildController.configure();
         // Currently need to force everything to be configured
         gradle.getServices().get(ProjectConfigurer.class).configureHierarchy(gradle.getRootProject());
diff --git a/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunnerTest.groovy b/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunnerTest.groovy
index 8e755e8..24f6234 100644
--- a/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunnerTest.groovy
+++ b/subprojects/tooling-api-builders/src/test/groovy/org/gradle/tooling/internal/provider/runner/ClientProvidedBuildActionRunnerTest.groovy
@@ -20,20 +20,20 @@ import org.gradle.StartParameter
 import org.gradle.api.BuildCancelledException
 import org.gradle.api.internal.GradleInternal
 import org.gradle.execution.ProjectConfigurer
+import org.gradle.initialization.BuildEventConsumer
 import org.gradle.internal.invocation.BuildController
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.tooling.internal.protocol.InternalBuildAction
 import org.gradle.tooling.internal.protocol.InternalBuildActionFailureException
 import org.gradle.tooling.internal.protocol.InternalBuildCancelledException
-import org.gradle.tooling.internal.provider.BuildActionResult
-import org.gradle.tooling.internal.provider.ClientProvidedBuildAction
-import org.gradle.tooling.internal.provider.PayloadSerializer
-import org.gradle.tooling.internal.provider.SerializedPayload
+import org.gradle.tooling.internal.provider.*
 import spock.lang.Specification
 
 class ClientProvidedBuildActionRunnerTest extends Specification {
-    def action = Mock(SerializedPayload)
     def startParameter = Mock(StartParameter)
+    def action = Mock(SerializedPayload)
+    def clientSubscriptions = Mock(BuildClientSubscriptions)
+    def buildEventConsumer = Mock(BuildEventConsumer)
     def payloadSerializer = Mock(PayloadSerializer)
     def projectConfigurer = Mock(ProjectConfigurer)
     def buildController = Mock(BuildController) {
@@ -41,10 +41,11 @@ class ClientProvidedBuildActionRunnerTest extends Specification {
             getServices() >> Stub(ServiceRegistry) {
                 get(PayloadSerializer) >> payloadSerializer
                 get(ProjectConfigurer) >> projectConfigurer
+                get(BuildEventConsumer) >> buildEventConsumer
             }
         }
     }
-    def clientProvidedBuildAction = new ClientProvidedBuildAction(startParameter, action)
+    def clientProvidedBuildAction = new ClientProvidedBuildAction(startParameter, action, clientSubscriptions)
     def runner = new ClientProvidedBuildActionRunner()
 
     def "can run action and returns result when completed"() {
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 6252e09..0802cd3 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
@@ -22,9 +22,11 @@ import org.gradle.integtests.fixtures.UsesSample
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.TextUtil
 import org.junit.Rule
 
+ at LeaksFileHandles
 class SamplesToolingApiIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule public final Sample sample = new Sample(temporaryFolder)
@@ -147,4 +149,4 @@ repositories {
             throw new IntegrationTestHint(e);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
index eda009a..4aee73c 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
@@ -26,6 +26,7 @@ import org.gradle.test.fixtures.file.TestFile
 import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.model.GradleProject
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Issue
 
 class ToolingApiIntegrationTest extends AbstractIntegrationSpec {
@@ -134,6 +135,7 @@ allprojects {
     }
 
     @Issue("GRADLE-2419")
+    @LeaksFileHandles
     def "tooling API does not hold JVM open"() {
         given:
         def buildFile = projectDir.file("build.gradle")
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 080a120..2a5223d 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
@@ -22,6 +22,7 @@ import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.tooling.*
 import org.gradle.tooling.internal.consumer.DefaultCancellationTokenSource
 import org.gradle.util.GradleVersion
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 import org.mortbay.jetty.MimeTypes
 
@@ -41,6 +42,7 @@ class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
         toolingApi.requireIsolatedUserHome()
     }
 
+    @LeaksFileHandles
     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"
 
@@ -152,4 +154,4 @@ class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
             println('server handler done.')
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ContinuousBuildToolingApiSpecification.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ContinuousBuildToolingApiSpecification.groovy
new file mode 100644
index 0000000..d4eb7a4
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ContinuousBuildToolingApiSpecification.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import org.apache.commons.io.output.TeeOutputStream
+import org.gradle.integtests.fixtures.executer.*
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.ProjectConnection
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Timeout
+import spock.util.concurrent.PollingConditions
+
+ at Timeout(180)
+ at Requires(TestPrecondition.JDK7_OR_LATER)
+ at TargetGradleVersion(GradleVersions.SUPPORTS_CONTINUOUS)
+ at ToolingApiVersion(ToolingApiVersions.SUPPORTS_CANCELLATION)
+abstract class ContinuousBuildToolingApiSpecification extends ToolingApiSpecification {
+
+    public static final String WAITING_MESSAGE = "Waiting for changes to input files of tasks..."
+
+    TestOutputStream stderr = new TestOutputStream()
+    TestOutputStream stdout = new TestOutputStream()
+
+    ExecutionResult result
+    ExecutionFailure failure
+
+    int buildTimeout = 10
+    def cancellationTokenSource = GradleConnector.newCancellationTokenSource()
+
+    TestResultHandler buildResult
+    TestFile sourceDir
+
+    ProjectConnection projectConnection
+
+    void setup() {
+        buildFile.text = "apply plugin: 'java'\n"
+        sourceDir = file("src/main/java")
+    }
+
+    @Override
+    <T> T withConnection(@DelegatesTo(ProjectConnection) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.ProjectConnection"]) Closure<T> cl) {
+        super.withConnection {
+            projectConnection = it
+            try {
+                it.with(cl)
+            } finally {
+                projectConnection = null
+            }
+        }
+    }
+
+    public <T> T runBuild(List<String> tasks = ["build"], Closure<T> underBuild) {
+        if (projectConnection) {
+            cancellationTokenSource = GradleConnector.newCancellationTokenSource()
+            buildResult = new TestResultHandler()
+            try {
+                // this is here to ensure that the lastModified() timestamps actually change in between builds.
+                // if the build is very fast, the timestamp of the file will not change and the JDK file watch service won't see the change.
+                def initScript = file("init.gradle")
+                initScript.text = """
+                    def startAt = System.currentTimeMillis()
+                    gradle.buildFinished {
+                        def sinceStart = System.currentTimeMillis() - startAt
+                        if (sinceStart < 2000) {
+                          sleep 2000 - sinceStart
+                        }
+                    }
+                """
+
+                BuildLauncher launcher = projectConnection.newBuild()
+                    .withArguments("--continuous", "-I", initScript.absolutePath)
+                    .forTasks(tasks as String[])
+                    .withCancellationToken(cancellationTokenSource.token())
+
+                if (toolingApi.isEmbedded()) {
+                    launcher
+                        .setStandardOutput(stdout)
+                        .setStandardError(stderr)
+                } else {
+                    launcher
+                        .setStandardOutput(new TeeOutputStream(stdout, System.out))
+                        .setStandardError(new TeeOutputStream(stderr, System.err))
+                }
+
+                customizeLauncher(launcher)
+
+                launcher.run(buildResult)
+                T t = underBuild.call()
+                cancellationTokenSource.cancel()
+                buildResult.finished(buildTimeout)
+                t
+            } finally {
+                cancellationTokenSource.cancel()
+            }
+        } else {
+            withConnection { runBuild(tasks, underBuild) }
+        }
+    }
+
+    void customizeLauncher(BuildLauncher launcher) {
+
+    }
+
+    ExecutionResult succeeds() {
+        waitForBuild()
+        if (result instanceof ExecutionFailure) {
+            throw new UnexpectedBuildFailure("build was expected to succeed but failed")
+        }
+        failure = null
+        result
+    }
+
+    ExecutionFailure fails() {
+        waitForBuild()
+        if (!(result instanceof ExecutionFailure)) {
+            throw new UnexpectedBuildFailure("build was expected to fail but succeeded")
+        }
+        failure = result as ExecutionFailure
+        failure
+    }
+
+    private void waitForBuild() {
+        new PollingConditions(initialDelay: 0.5).within(buildTimeout) {
+            assert stdout.toString().contains(WAITING_MESSAGE)
+        }
+
+        def out = stdout.toString()
+        stdout.reset()
+        def err = stderr.toString()
+        stderr.reset()
+
+        result = out.contains("BUILD SUCCESSFUL") ? new OutputScrapingExecutionResult(out, err) : new OutputScrapingExecutionFailure(out, err)
+    }
+
+    protected List<String> getExecutedTasks() {
+        assertHasResult()
+        result.executedTasks
+    }
+
+    private assertHasResult() {
+        assert result != null: "result is null, you haven't run succeeds()"
+    }
+
+    protected Set<String> getSkippedTasks() {
+        assertHasResult()
+        result.skippedTasks
+    }
+
+    protected List<String> getNonSkippedTasks() {
+        executedTasks - skippedTasks
+    }
+
+    protected void executedAndNotSkipped(String... tasks) {
+        tasks.each {
+            assert it in executedTasks
+            assert !skippedTasks.contains(it)
+        }
+    }
+
+    boolean cancel() {
+        cancellationTokenSource.cancel()
+        true
+    }
+}
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 145e02e..6b56ddc 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,11 +15,11 @@
  */
 package org.gradle.integtests.tooling.fixture
 
+import org.gradle.integtests.fixtures.daemon.DaemonLogsAnalyzer
+import org.gradle.integtests.fixtures.daemon.DaemonsFixture
 import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
-import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
-import org.gradle.launcher.daemon.testing.DaemonsFixture
 import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.tooling.GradleConnector
@@ -42,13 +42,12 @@ class ToolingApi implements TestRule {
     private TestFile gradleUserHomeDir
     private TestFile daemonBaseDir
     private boolean useSeparateDaemonBaseDir
-    private boolean inProcess;
+    private boolean inProcess
     private boolean requiresDaemon
     private boolean requireIsolatedDaemons
 
     private final List<Closure> connectorConfigurers = []
     boolean verboseLogging = LOGGER.debugEnabled
-    List<DefaultGradleConnector> connectors = new ArrayList<DefaultGradleConnector>();
 
     ToolingApi(GradleDistribution dist, TestDirectoryProvider testWorkDirProvider) {
         this.dist = dist
@@ -132,7 +131,7 @@ class ToolingApi implements TestRule {
     private <T> T withConnectionRaw(GradleConnector connector, Closure<T> cl) {
         ProjectConnection connection = connector.connect()
         try {
-            return cl.call(connection)
+            return connection.with(cl)
         } catch (Throwable t) {
             validate(t)
             throw t
@@ -153,7 +152,7 @@ class ToolingApi implements TestRule {
         if (connector.metaClass.hasProperty(connector, 'verboseLogging')) {
             connector.verboseLogging = verboseLogging
         }
-        if (!requiresDaemon && GradleVersion.current() == dist.version) {
+        if (isEmbedded()) {
             println("Using embedded tooling API provider from ${GradleVersion.current().version} to classpath (${dist.version.version})")
             connector.useClasspathDistribution()
             connector.embedded(true)
@@ -163,11 +162,15 @@ class ToolingApi implements TestRule {
             connector.embedded(false)
         }
         connectorConfigurers.each {
-            it.call(connector)
+            connector.with(it)
         }
         return connector
     }
 
+    boolean isEmbedded() {
+        !requiresDaemon && GradleVersion.current() == dist.version
+    }
+
     @Override
     Statement apply(Statement base, Description description) {
         return new Statement() {
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 b6723d4..5d40492 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,6 +15,7 @@
  */
 package org.gradle.integtests.tooling.fixture
 
+import org.apache.commons.io.output.TeeOutputStream
 import org.gradle.api.specs.Spec
 import org.gradle.api.specs.Specs
 import org.gradle.integtests.fixtures.AbstractCompatibilityTestRunner
@@ -25,6 +26,7 @@ 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.launcher.exec.DaemonUsageSuggestingBuildActionExecuter
 import org.gradle.util.*
 
 class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner {
@@ -147,6 +149,8 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
             sharedClassLoader.allowClass(TestPrecondition)
             sharedClassLoader.allowClass(TargetGradleVersion)
             sharedClassLoader.allowClass(ToolingApiVersion)
+            sharedClassLoader.allowClass(DaemonUsageSuggestingBuildActionExecuter)
+            sharedClassLoader.allowClass(TeeOutputStream)
             sharedClassLoader.allowResources(target.name.replace('.', '/'))
 
             def parentClassLoader = new MultiParentClassLoader(toolingApi.classLoader, sharedClassLoader)
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
index 38cfca4..a7dc200 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
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.tooling.fixture
 
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
 import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
 import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
@@ -56,6 +58,8 @@ abstract class ToolingApiSpecification extends Specification {
     TestDistributionDirectoryProvider temporaryDistributionFolder = new TestDistributionDirectoryProvider();
     final ToolingApi toolingApi = new ToolingApi(targetDist, temporaryFolder)
 
+    final GradleVersion toolingApiVersion = GradleVersion.current() // works due to classloading arrangement by ToolingApiCompatibilitySuiteRunner
+
     @Rule
     public RuleChain chain = RuleChain.outerRule(temporaryFolder).around(temporaryDistributionFolder).around(toolingApi);
 
@@ -71,15 +75,15 @@ abstract class ToolingApiSpecification extends Specification {
         new ConnectorServices().reset()
     }
 
-    public void withConnector(@DelegatesTo(GradleConnector) Closure cl) {
+    public void withConnector(@DelegatesTo(GradleConnector) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.GradleConnector"]) Closure cl) {
         toolingApi.withConnector(cl)
     }
 
-    public <T> T withConnection(@DelegatesTo(ProjectConnection) Closure<T> cl) {
+    public <T> T withConnection(@DelegatesTo(ProjectConnection) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.ProjectConnection"]) Closure<T> cl) {
         toolingApi.withConnection(cl)
     }
 
-    public <T> T withConnection(GradleConnector connector, @DelegatesTo(ProjectConnection) Closure<T> cl) {
+    public <T> T withConnection(GradleConnector connector, @DelegatesTo(ProjectConnection) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.ProjectConnection"]) Closure<T> cl) {
         toolingApi.withConnection(connector, cl)
     }
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersions.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersions.java
new file mode 100644
index 0000000..1bc78d2
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiVersions.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Values are intended to be used with {@link ToolingApiVersion}.
+ */
+public class ToolingApiVersions {
+
+    private ToolingApiVersions() {
+    }
+
+    public static final String SUPPORTS_CANCELLATION = ">=2.1";
+    public static final String SUPPORTS_RICH_PROGRESS_EVENTS = ">=2.5";
+    public static final String PRE_CANCELLATION = ">=1.2 <2.1";
+
+}
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 1b3d88a..efcddcf 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
@@ -17,7 +17,6 @@
 package org.gradle.integtests.tooling.m8
 
 import org.gradle.integtests.fixtures.executer.ExecutionResult
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.junit.Assume
 
@@ -112,13 +111,10 @@ project.logger.debug("debug logging");
     }
 
     private ExecutionResult runUsingCommandLine() {
-        def executer = targetDist.executer(temporaryFolder)
-        if (!GradleContextualExecuter.longLivingProcess) {
-            //suppress daemon usage suggestions
-            executer.withArgument("--no-daemon")
-        }
-        executer.withGradleOpts("-Dorg.gradle.daemon.disable-starting-message=true")
-        executer.run()
+        targetDist.executer(temporaryFolder)
+            .withArgument("--no-daemon") //suppress daemon usage suggestions
+            .withGradleOpts("-Dorg.gradle.daemon.disable-starting-message=true")
+            .run()
     }
 
     String normaliseOutput(String output) {
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 1dcdbe4..eb8e55f 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
@@ -20,12 +20,14 @@ import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.tooling.GradleConnector
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import spock.lang.Issue
 /**
  * Tests that init scripts are used from the _clients_ GRADLE_HOME, not the daemon server's.
  */
 @TargetGradleVersion('>=1.4')
 @Issue("https://issues.gradle.org/browse/GRADLE-2408")
+ at LeaksFileHandles
 class ToolingApiInitScriptCrossVersionIntegrationTest extends ToolingApiSpecification {
 
     TestFile createDistribution(int i) {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r22/BuildActionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r22/BuildActionCrossVersionSpec.groovy
index 153af5d..22996fc 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r22/BuildActionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r22/BuildActionCrossVersionSpec.groovy
@@ -24,8 +24,10 @@ import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.tooling.BuildAction
 import org.gradle.tooling.BuildController
 import org.gradle.tooling.ProjectConnection
+import org.gradle.test.fixtures.file.LeaksFileHandles
 
 @ToolingApiVersion(">=1.8")
+ at LeaksFileHandles
 class BuildActionCrossVersionSpec extends ToolingApiSpecification {
     @TargetGradleVersion(">=2.2")
     def "can change the implementation of an action"() {
@@ -66,8 +68,7 @@ public class ActionImpl implements ${BuildAction.name}<java.io.File> {
         when:
         // Discard the impl jar from the jvm's jar file cache
         forceJarClose(implJar)
-        workDir.deleteDir()
-        builder.sourceFile('ActionImpl.java') << """
+        builder.sourceFile('ActionImpl.java').text = """
 public class ActionImpl implements ${BuildAction.name}<String> {
     public String execute(${BuildController.name} controller) {
         return getClass().getProtectionDomain().getCodeSource().getLocation().toString();
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/DaemonUsageSuggestionCrossVersionTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/DaemonUsageSuggestionCrossVersionTest.groovy
index 72316ce..0d3d916 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/DaemonUsageSuggestionCrossVersionTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/DaemonUsageSuggestionCrossVersionTest.groovy
@@ -16,22 +16,16 @@
 
 package org.gradle.integtests.tooling.r24
 
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.integtests.tooling.fixture.ConfigurableOperation
 import org.gradle.integtests.tooling.fixture.TargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.integtests.tooling.fixture.ToolingApiVersion
 import org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter
 import org.gradle.tooling.ProjectConnection
-import org.gradle.util.TestPrecondition
-import spock.lang.IgnoreIf
 
- at IgnoreIf({ !GradleContextualExecuter.embedded || TestPrecondition.WINDOWS.fulfilled })
- at ToolingApiVersion(">=2.4")
 @TargetGradleVersion(">=2.4")
 class DaemonUsageSuggestionCrossVersionTest extends ToolingApiSpecification {
 
-    def "does not print suggestion to use the daemon when using tooling api in embedded mode"() {
+    def "does not print suggestion to use the daemon when using tooling api"() {
         when:
         def operation = withConnection { ProjectConnection connection ->
             def build = connection.newBuild()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressCrossVersionSpec.groovy
index 649ad45..d4fcb18 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressCrossVersionSpec.groovy
@@ -15,46 +15,23 @@
  */
 package org.gradle.integtests.tooling.r24
 
-import groovy.transform.NotYetImplemented
 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.file.TestFile
 import org.gradle.tooling.GradleConnectionException
 import org.gradle.tooling.ProjectConnection
-import org.gradle.tooling.events.FinishEvent
-import org.gradle.tooling.events.StartEvent
-import org.gradle.tooling.events.test.JvmTestKind
-import org.gradle.tooling.events.test.TestFailureResult
-import org.gradle.tooling.events.test.TestProgressEvent
-import org.gradle.tooling.events.test.TestProgressListener
-import org.gradle.tooling.events.test.TestSkippedResult
-import org.gradle.tooling.events.test.TestSuccessResult
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.test.*
 import org.gradle.tooling.model.gradle.BuildInvocations
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 
 import java.util.concurrent.ConcurrentLinkedQueue
 
+ at ToolingApiVersion("=2.4")
+ at TargetGradleVersion(">=2.4")
 class TestProgressCrossVersionSpec extends ToolingApiSpecification {
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=1.0-milestone-8 <2.4")
-    def "ignores listeners when Gradle version does not generate test events"() {
-        given:
-        goodCode()
-
-        when:
-        withConnection {
-            ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener({ throw new RuntimeException() } as TestProgressListener).run()
-        }
-
-        then:
-        noExceptionThrown()
-    }
-
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "receive test progress events when requesting a model"() {
         given:
         goodCode()
@@ -63,20 +40,15 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.model(BuildInvocations.class).forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        result.add(event)
-                    }
-                }).get()
+                connection.model(BuildInvocations.class).forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.get()
         }
 
         then: "test progress events must be forwarded to the attached listeners"
         result.size() > 0
     }
 
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "receive test progress events when launching a build"() {
         given:
         goodCode()
@@ -85,76 +57,15 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        result.add(event)
-                    }
-                }).run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.run()
         }
 
         then: "test progress events must be forwarded to the attached listeners"
         result.size() > 0
     }
 
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
-    def "build aborts if a test listener throws an exception"() {
-        given:
-        goodCode()
-
-        when: "launching a build"
-        withConnection {
-            ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        throw new IllegalStateException("Throwing an exception on purpose")
-                    }
-                }).run()
-        }
-
-        then: "build aborts if the test listener throws an exception"
-        thrown(GradleConnectionException)
-    }
-
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
-    def "receive current test progress event even if one of multiple test listeners throws an exception"() {
-        given:
-        goodCode()
-
-        when: "launching a build"
-        List<TestProgressEvent> resultsOfFirstListener = new ArrayList<TestProgressEvent>()
-        List<TestProgressEvent> resultsOfLastListener = new ArrayList<TestProgressEvent>()
-        withConnection {
-            ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        resultsOfFirstListener.add(event)
-                    }
-                }).addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        throw new IllegalStateException("Throwing an exception on purpose")
-                    }
-                }).addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        resultsOfLastListener.add(event)
-                    }
-                }).run()
-        }
-
-        then: "current test progress event must still be forwarded to the attached listeners even if one of the listeners throws an exception"
-        thrown(GradleConnectionException)
-        resultsOfFirstListener.size() == 1
-        resultsOfLastListener.size() == 1
-    }
-
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "receive test progress events for successful test run"() {
         given:
         buildFile << """
@@ -178,13 +89,9 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        assert event != null
-                        result.add(event)
-                    }
-                }).run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.run()
         }
 
         then:
@@ -196,89 +103,87 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         }
 
         def rootStartedEvent = result[0]
-        rootStartedEvent instanceof StartEvent &&
-                rootStartedEvent.eventTime > 0 &&
-                rootStartedEvent.displayName == "Gradle Test Run :test started" &&
-                rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.className == null &&
-                rootStartedEvent.descriptor.methodName == null &&
-                rootStartedEvent.descriptor.parent == null
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
         def testProcessStartedEvent = result[1]
-        testProcessStartedEvent instanceof StartEvent &&
-                testProcessStartedEvent.eventTime > 0 &&
-                testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
-                testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.className == null &&
-                testProcessStartedEvent.descriptor.methodName == null &&
-                testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
         def testClassStartedEvent = result[2]
-        testClassStartedEvent instanceof StartEvent &&
-                testClassStartedEvent.eventTime > 0 &&
-                testClassStartedEvent.displayName == "Test class example.MyTest started" &&
-                testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testClassStartedEvent.descriptor.name == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
-                testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.className == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.methodName == null &&
-                testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
         def testStartedEvent = result[3]
-        testStartedEvent instanceof StartEvent &&
-                testStartedEvent.eventTime > 0 &&
-                testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
-                testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
-                testStartedEvent.descriptor.name == 'foo' &&
-                testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
-                testStartedEvent.descriptor.suiteName == null &&
-                testStartedEvent.descriptor.className == 'example.MyTest' &&
-                testStartedEvent.descriptor.methodName == 'foo' &&
-                testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
         def testSucceededEvent = result[4]
-        testSucceededEvent instanceof FinishEvent &&
-                testSucceededEvent.eventTime >= testSucceededEvent.result.endTime &&
-                testSucceededEvent.displayName == "Test foo(example.MyTest) succeeded" &&
-                testSucceededEvent.descriptor == testStartedEvent.descriptor &&
-                testSucceededEvent.result instanceof TestSuccessResult &&
-                testSucceededEvent.result.startTime == testStartedEvent.eventTime &&
-                testSucceededEvent.result.endTime > testSucceededEvent.result.startTime &&
-                testSucceededEvent.result.endTime == testSucceededEvent.eventTime
+        testSucceededEvent instanceof TestFinishEvent &&
+            testSucceededEvent.eventTime >= testSucceededEvent.result.endTime &&
+            testSucceededEvent.displayName == "Test foo(example.MyTest) succeeded" &&
+            testSucceededEvent.descriptor == testStartedEvent.descriptor &&
+            testSucceededEvent.result instanceof TestSuccessResult &&
+            testSucceededEvent.result.startTime == testStartedEvent.eventTime &&
+            testSucceededEvent.result.endTime > testSucceededEvent.result.startTime &&
+            testSucceededEvent.result.endTime == testSucceededEvent.eventTime
         def testClassSucceededEvent = result[5]
-        testClassSucceededEvent instanceof FinishEvent &&
-                testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
-                testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
-                testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
-                testClassSucceededEvent.result instanceof TestSuccessResult &&
-                testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
-                testClassSucceededEvent.result.endTime > testClassSucceededEvent.result.startTime &&
-                testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
+        testClassSucceededEvent instanceof TestFinishEvent &&
+            testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
+            testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
+            testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassSucceededEvent.result instanceof TestSuccessResult &&
+            testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassSucceededEvent.result.endTime > testClassSucceededEvent.result.startTime &&
+            testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
         def testProcessSucceededEvent = result[6]
-        testProcessSucceededEvent instanceof FinishEvent &&
-                testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
-                testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
-                testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
-                testProcessSucceededEvent.result instanceof TestSuccessResult &&
-                testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
-                testProcessSucceededEvent.result.endTime > testProcessSucceededEvent.result.startTime &&
-                testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
+        testProcessSucceededEvent instanceof TestFinishEvent &&
+            testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
+            testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
+            testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessSucceededEvent.result instanceof TestSuccessResult &&
+            testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessSucceededEvent.result.endTime > testProcessSucceededEvent.result.startTime &&
+            testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
         def rootSucceededEvent = result[7]
-        rootSucceededEvent instanceof FinishEvent &&
-                rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
-                rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
-                rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
-                rootSucceededEvent.result instanceof TestSuccessResult &&
-                rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
-                rootSucceededEvent.result.endTime > rootSucceededEvent.result.startTime &&
-                rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
+        rootSucceededEvent instanceof TestFinishEvent &&
+            rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
+            rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
+            rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
+            rootSucceededEvent.result instanceof TestSuccessResult &&
+            rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootSucceededEvent.result.endTime > rootSucceededEvent.result.startTime &&
+            rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
     }
 
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "receive test progress events for failed test run"() {
         given:
         buildFile << """
@@ -303,13 +208,9 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        assert event != null
-                        result.add(event)
-                    }
-                }).run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.run()
         }
 
         then:
@@ -321,95 +222,93 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         }
 
         def rootStartedEvent = result[0]
-        rootStartedEvent instanceof StartEvent &&
-                rootStartedEvent.eventTime > 0 &&
-                rootStartedEvent.displayName == "Gradle Test Run :test started" &&
-                rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.className == null &&
-                rootStartedEvent.descriptor.methodName == null &&
-                rootStartedEvent.descriptor.parent == null
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
         def testProcessStartedEvent = result[1]
-        testProcessStartedEvent instanceof StartEvent &&
-                testProcessStartedEvent.eventTime > 0 &&
-                testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
-                testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.className == null &&
-                testProcessStartedEvent.descriptor.methodName == null &&
-                testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
         def testClassStartedEvent = result[2]
-        testClassStartedEvent instanceof StartEvent &&
-                testClassStartedEvent.eventTime > 0 &&
-                testClassStartedEvent.displayName == "Test class example.MyTest started" &&
-                testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testClassStartedEvent.descriptor.name == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
-                testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.className == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.methodName == null &&
-                testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
         def testStartedEvent = result[3]
-        testStartedEvent instanceof StartEvent &&
-                testStartedEvent.eventTime > 0 &&
-                testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
-                testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
-                testStartedEvent.descriptor.name == 'foo' &&
-                testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
-                testStartedEvent.descriptor.suiteName == null &&
-                testStartedEvent.descriptor.className == 'example.MyTest' &&
-                testStartedEvent.descriptor.methodName == 'foo' &&
-                testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
         def testFailedEvent = result[4]
-        testFailedEvent instanceof FinishEvent &&
-                testFailedEvent.eventTime >= testFailedEvent.result.endTime &&
-                testFailedEvent.displayName == "Test foo(example.MyTest) failed" &&
-                testFailedEvent.descriptor == testStartedEvent.descriptor &&
-                testFailedEvent.result instanceof TestFailureResult &&
-                testFailedEvent.result.startTime == testStartedEvent.eventTime &&
-                testFailedEvent.result.endTime == testFailedEvent.eventTime &&
-                testFailedEvent.result.failures.size() == 1 &&
-                testFailedEvent.result.failures[0].message == 'broken' &&
-                testFailedEvent.result.failures[0].description.startsWith("java.lang.RuntimeException: broken") &&
-                testFailedEvent.result.failures[0].description.contains("at example.MyTest.foo(MyTest.java:6)") &&
-                testFailedEvent.result.failures[0].causes.size() == 1 &&
-                testFailedEvent.result.failures[0].causes[0].message == 'nope' &&
-                testFailedEvent.result.failures[0].causes[0].causes.empty
+        testFailedEvent instanceof TestFinishEvent &&
+            testFailedEvent.eventTime >= testFailedEvent.result.endTime &&
+            testFailedEvent.displayName == "Test foo(example.MyTest) failed" &&
+            testFailedEvent.descriptor == testStartedEvent.descriptor &&
+            testFailedEvent.result instanceof TestFailureResult &&
+            testFailedEvent.result.startTime == testStartedEvent.eventTime &&
+            testFailedEvent.result.endTime == testFailedEvent.eventTime &&
+            testFailedEvent.result.failures.size() == 1 &&
+            testFailedEvent.result.failures[0].message == 'broken' &&
+            testFailedEvent.result.failures[0].description.startsWith("java.lang.RuntimeException: broken") &&
+            testFailedEvent.result.failures[0].description.contains("at example.MyTest.foo(MyTest.java:6)") &&
+            testFailedEvent.result.failures[0].causes.size() == 1 &&
+            testFailedEvent.result.failures[0].causes[0].message == 'nope' &&
+            testFailedEvent.result.failures[0].causes[0].causes.empty
         def testClassFailedEvent = result[5]
-        testClassFailedEvent instanceof FinishEvent &&
-                testClassFailedEvent.eventTime >= testClassFailedEvent.result.endTime &&
-                testClassFailedEvent.displayName == "Test class example.MyTest failed" &&
-                testClassFailedEvent.descriptor == testClassStartedEvent.descriptor &&
-                testClassFailedEvent.result instanceof TestFailureResult &&
-                testClassFailedEvent.result.startTime == testClassStartedEvent.eventTime &&
-                testClassFailedEvent.result.endTime == testClassFailedEvent.eventTime &&
-                testClassFailedEvent.result.failures.size() == 0
+        testClassFailedEvent instanceof TestFinishEvent &&
+            testClassFailedEvent.eventTime >= testClassFailedEvent.result.endTime &&
+            testClassFailedEvent.displayName == "Test class example.MyTest failed" &&
+            testClassFailedEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassFailedEvent.result instanceof TestFailureResult &&
+            testClassFailedEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassFailedEvent.result.endTime == testClassFailedEvent.eventTime &&
+            testClassFailedEvent.result.failures.size() == 0
         def testProcessFailedEvent = result[6]
-        testProcessFailedEvent instanceof FinishEvent &&
-                testProcessFailedEvent.eventTime >= testProcessFailedEvent.result.endTime &&
-                testProcessFailedEvent.displayName == "Gradle Test Executor 2 failed" &&
-                testProcessFailedEvent.descriptor == testProcessStartedEvent.descriptor &&
-                testProcessFailedEvent.result instanceof TestFailureResult &&
-                testProcessFailedEvent.result.startTime == testProcessStartedEvent.eventTime &&
-                testProcessFailedEvent.result.endTime == testProcessFailedEvent.eventTime &&
-                testProcessFailedEvent.result.failures.size() == 0
+        testProcessFailedEvent instanceof TestFinishEvent &&
+            testProcessFailedEvent.eventTime >= testProcessFailedEvent.result.endTime &&
+            testProcessFailedEvent.displayName == "Gradle Test Executor 2 failed" &&
+            testProcessFailedEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessFailedEvent.result instanceof TestFailureResult &&
+            testProcessFailedEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessFailedEvent.result.endTime == testProcessFailedEvent.eventTime &&
+            testProcessFailedEvent.result.failures.size() == 0
         def rootFailedEvent = result[7]
-        rootFailedEvent instanceof FinishEvent &&
-                rootFailedEvent.eventTime >= rootFailedEvent.result.endTime &&
-                rootFailedEvent.displayName == "Gradle Test Run :test failed" &&
-                rootFailedEvent.descriptor == rootStartedEvent.descriptor &&
-                rootFailedEvent.result instanceof TestFailureResult &&
-                rootFailedEvent.result.startTime == rootStartedEvent.eventTime &&
-                rootFailedEvent.result.endTime == rootFailedEvent.eventTime &&
-                rootFailedEvent.result.failures.size() == 0
+        rootFailedEvent instanceof TestFinishEvent &&
+            rootFailedEvent.eventTime >= rootFailedEvent.result.endTime &&
+            rootFailedEvent.displayName == "Gradle Test Run :test failed" &&
+            rootFailedEvent.descriptor == rootStartedEvent.descriptor &&
+            rootFailedEvent.result instanceof TestFailureResult &&
+            rootFailedEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootFailedEvent.result.endTime == rootFailedEvent.eventTime &&
+            rootFailedEvent.result.failures.size() == 0
     }
 
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "receive test progress events for skipped test run"() {
         given:
         buildFile << """
@@ -433,13 +332,9 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        assert event != null
-                        result.add(event)
-                    }
-                }).run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.run()
         }
 
         then:
@@ -451,85 +346,83 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         }
 
         def rootStartedEvent = result[0]
-        rootStartedEvent instanceof StartEvent &&
-                rootStartedEvent.eventTime > 0 &&
-                rootStartedEvent.displayName == "Gradle Test Run :test started" &&
-                rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
-                rootStartedEvent.descriptor.className == null &&
-                rootStartedEvent.descriptor.methodName == null &&
-                rootStartedEvent.descriptor.parent == null
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
         def testProcessStartedEvent = result[1]
-        testProcessStartedEvent instanceof StartEvent &&
-                testProcessStartedEvent.eventTime > 0 &&
-                testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
-                testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
-                testProcessStartedEvent.descriptor.className == null &&
-                testProcessStartedEvent.descriptor.methodName == null &&
-                testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
         def testClassStartedEvent = result[2]
-        testClassStartedEvent instanceof StartEvent &&
-                testClassStartedEvent.eventTime > 0 &&
-                testClassStartedEvent.displayName == "Test class example.MyTest started" &&
-                testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
-                testClassStartedEvent.descriptor.name == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.displayName == "Test class example.MyTest" &&
-                testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.className == 'example.MyTest' &&
-                testClassStartedEvent.descriptor.methodName == null &&
-                testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == "Test class example.MyTest" &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
         def testStartedEvent = result[3]
-        testStartedEvent instanceof StartEvent &&
-                testStartedEvent.eventTime > 0 &&
-                testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
-                testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
-                testStartedEvent.descriptor.name == 'foo' &&
-                testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
-                testStartedEvent.descriptor.suiteName == null &&
-                testStartedEvent.descriptor.className == 'example.MyTest' &&
-                testStartedEvent.descriptor.methodName == 'foo' &&
-                testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
         def testSkippedEvent = result[4]
-        testSkippedEvent instanceof FinishEvent &&
-                testSkippedEvent.eventTime > 0 &&
-                testSkippedEvent.displayName == "Test foo(example.MyTest) skipped" &&
-                testSkippedEvent.descriptor == testStartedEvent.descriptor &&
-                testSkippedEvent.result instanceof TestSkippedResult &&
-                testSkippedEvent.result.startTime == testStartedEvent.eventTime &&
-                testSkippedEvent.result.endTime == testSkippedEvent.eventTime
+        testSkippedEvent instanceof TestFinishEvent &&
+            testSkippedEvent.eventTime > 0 &&
+            testSkippedEvent.displayName == "Test foo(example.MyTest) skipped" &&
+            testSkippedEvent.descriptor == testStartedEvent.descriptor &&
+            testSkippedEvent.result instanceof TestSkippedResult &&
+            testSkippedEvent.result.startTime == testStartedEvent.eventTime &&
+            testSkippedEvent.result.endTime == testSkippedEvent.eventTime
         def testClassSucceededEvent = result[5]
-        testClassSucceededEvent instanceof FinishEvent &&
-                testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
-                testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
-                testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
-                testClassSucceededEvent.result instanceof TestSuccessResult &&
-                testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
-                testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
+        testClassSucceededEvent instanceof TestFinishEvent &&
+            testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
+            testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
+            testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassSucceededEvent.result instanceof TestSuccessResult &&
+            testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
         def testProcessSucceededEvent = result[6]
-        testProcessSucceededEvent instanceof FinishEvent &&
-                testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
-                testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
-                testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
-                testProcessSucceededEvent.result instanceof TestSuccessResult &&
-                testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
-                testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
+        testProcessSucceededEvent instanceof TestFinishEvent &&
+            testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
+            testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
+            testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessSucceededEvent.result instanceof TestSuccessResult &&
+            testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
         def rootSucceededEvent = result[7]
-        rootSucceededEvent instanceof FinishEvent &&
-                rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
-                rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
-                rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
-                rootSucceededEvent.result instanceof TestSuccessResult &&
-                rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
-                rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
+        rootSucceededEvent instanceof TestFinishEvent &&
+            rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
+            rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
+            rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
+            rootSucceededEvent.result instanceof TestSuccessResult &&
+            rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
     }
 
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "test progress event ids are unique across multiple test workers"() {
         given:
         buildFile << """
@@ -587,13 +480,9 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         Queue<TestProgressEvent> result = new ConcurrentLinkedQueue<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        assert event != null
-                        result.add(event)
-                    }
-                }).run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.run()
         }
 
         then: "start and end event is sent for each node in the test tree"
@@ -608,8 +497,6 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
     }
 
     @Requires(TestPrecondition.NOT_WINDOWS)
-    @ToolingApiVersion(">=2.4")
-    @TargetGradleVersion(">=2.4")
     def "test progress event ids are unique across multiple test tasks, even when run in parallel"() {
         given:
         projectDir.createFile('settings.gradle') << """
@@ -667,13 +554,9 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         Queue<TestProgressEvent> result = new ConcurrentLinkedQueue<TestProgressEvent>()
         withConnection {
             ProjectConnection connection ->
-                connection.newBuild().forTasks('test').addTestProgressListener(new TestProgressListener() {
-                    @Override
-                    void statusChanged(TestProgressEvent event) {
-                        assert event != null
-                        result.add(event)
-                    }
-                }).withArguments('--parallel').run()
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    result << event
+                }.withArguments('--parallel').run()
         }
 
         then: "start and end event is sent for each node in the test tree"
@@ -691,6 +574,42 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
         result.findAll { it.descriptor.name =~ 'Gradle Test Executor \\d+' }.toSet().size() == 8       // 2 test processes for each task (start & finish events)
     }
 
+    @ToolingApiVersion("=2.4")
+    @TargetGradleVersion(">=2.5")
+    def "stops dispatching events to progress listeners when a listener fails and continues with build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<ProgressEvent> resultsOfFirstListener = []
+        List<ProgressEvent> resultsOfLastListener = []
+        def failure = new IllegalStateException("Throwing an exception on purpose")
+        def stdout = new ByteArrayOutputStream()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addTestProgressListener { TestProgressEvent event ->
+                    resultsOfFirstListener.add(event)
+                }.addTestProgressListener { TestProgressEvent event ->
+                    throw failure
+                }.addTestProgressListener{ TestProgressEvent event ->
+                    resultsOfLastListener.add(event)
+                }.setStandardOutput(stdout).run()
+        }
+
+        then: "build completes with an exception but events are received"
+        def e = thrown(GradleConnectionException)
+        e.message.startsWith("Could not execute build using Gradle installation")
+        e.cause.message == "One or more progress listeners failed with an exception."
+        e.cause.cause == failure
+
+        and: "expected events received"
+        resultsOfFirstListener.size() == 1
+        resultsOfLastListener.size() == 1
+
+        and: "build execution is successful"
+        stdout.toString().contains("BUILD SUCCESSFUL")
+    }
+
     def goodCode() {
         buildFile << """
             apply plugin: 'java'
@@ -708,37 +627,4 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification {
             }
         """
     }
-
-    @TargetGradleVersion('>=2.4')
-    @ToolingApiVersion('>=2.4')
-    @NotYetImplemented
-    def "should receive test events from buildSrc"() {
-        buildFile << """task dummy()"""
-        file("buildSrc/build.gradle") << """
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile 'junit:junit:4.12' }
-            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
-        """
-        file("buildSrc/src/test/java/example/MyTest.java") << """
-            package example;
-            public class MyTest {
-                @org.junit.Test public void foo() throws Exception {
-                     org.junit.Assert.assertEquals(1, 1);
-                }
-            }
-        """
-
-        when:
-        def result = []
-        withConnection { ProjectConnection connection ->
-            connection.newBuild().forTasks('dummy').addTestProgressListener { TestProgressEvent event ->
-                assert event != null
-                result << event
-            }.run()
-        }
-
-        then:
-        !result.empty
-    }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressDaemonErrorsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressDaemonErrorsCrossVersionSpec.groovy
index d840cf7..a95e381 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressDaemonErrorsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r24/TestProgressDaemonErrorsCrossVersionSpec.groovy
@@ -26,30 +26,12 @@ import org.gradle.tooling.events.test.TestProgressEvent
 
 class TestProgressDaemonErrorsCrossVersionSpec extends ToolingApiSpecification {
 
-    def goodCode() {
-        buildFile << """
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile 'junit:junit:4.12' }
-            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
-        """
-
-        file("src/test/java/example/MyTest.java") << """
-            package example;
-            public class MyTest {
-                @org.junit.Test public void foo() throws Exception {
-                     org.junit.Assert.assertEquals(1, 1);
-                }
-            }
-        """
-    }
-
     void setup() {
         toolingApi.requireIsolatedDaemons()
     }
 
     @TargetGradleVersion('>=2.4')
-    @ToolingApiVersion('>=2.4')
+    @ToolingApiVersion('=2.4')
     def "should received failed event when daemon disappears unexpectedly"() {
         given:
         goodCode()
@@ -67,9 +49,25 @@ class TestProgressDaemonErrorsCrossVersionSpec extends ToolingApiSpecification {
         GradleConnectionException ex = thrown()
         ex.cause.message.contains('Gradle build daemon disappeared unexpectedly')
 
-        and: "a single event was received"
-        result.size() == 1
+        and:
+        !result.empty
     }
 
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+        """
+
+        file("src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+    }
 
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/BuildProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/BuildProgressCrossVersionSpec.groovy
new file mode 100644
index 0000000..f8701fe
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/BuildProgressCrossVersionSpec.groovy
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import groovy.transform.NotYetImplemented
+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.ListenerFailedException
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.events.*
+import org.gradle.tooling.model.gradle.BuildInvocations
+
+class BuildProgressCrossVersionSpec extends ToolingApiSpecification {
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=1.0-milestone-8 <2.5")
+    def "ignores listeners when Gradle version does not generate build events"() {
+        given:
+        goodCode()
+
+        when:
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    throw new RuntimeException()
+                }, EnumSet.of(OperationType.GENERIC)).run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive build progress events when requesting a model"() {
+        given:
+        goodCode()
+
+        when: "asking for a model and specifying some task(s) to run first"
+        List<ProgressEvent> result = []
+        withConnection {
+            ProjectConnection connection ->
+                connection.model(BuildInvocations).forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << event
+                }, EnumSet.of(OperationType.GENERIC)).get()
+        }
+
+        then: "build progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive build progress events when launching a build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<ProgressEvent> result = []
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << event
+                }, EnumSet.of(OperationType.GENERIC)).run()
+        }
+
+        then: "build progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "stops dispatching events to progress listeners when a listener fails and continues with build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<ProgressEvent> resultsOfFirstListener = []
+        List<ProgressEvent> resultsOfLastListener = []
+        def stdout = new ByteArrayOutputStream()
+        def failure = new IllegalStateException("Throwing an exception on purpose")
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    resultsOfFirstListener.add(event)
+                }, EnumSet.of(OperationType.GENERIC)).addProgressListener({ ProgressEvent event ->
+                    throw failure
+                }, EnumSet.of(OperationType.GENERIC)).addProgressListener({ ProgressEvent event ->
+                    resultsOfLastListener.add(event)
+                }, EnumSet.of(OperationType.GENERIC)).setStandardOutput(stdout).run()
+        }
+
+        then: "listener exception is wrapped"
+        ListenerFailedException ex = thrown()
+        ex.message.startsWith("Could not execute build using")
+        ex.causes == [failure]
+
+        and: "expected events received"
+        resultsOfFirstListener.size() == 1
+        resultsOfLastListener.size() == 1
+
+        and: "build execution is successful"
+        stdout.toString().contains("BUILD SUCCESSFUL")
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive build progress events for successful operations"() {
+        given:
+        goodCode()
+
+        when:
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('classes').addProgressListener({ ProgressEvent event ->
+                    result << event
+                }, EnumSet.of(OperationType.GENERIC)).run()
+        }
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 6 * 2          // build running, init scripts, loading, configuring, populating task graph, executing tasks
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def buildRunningStarted = result[0]
+        buildRunningStarted instanceof StartEvent &&
+            buildRunningStarted.eventTime > 0 &&
+            buildRunningStarted.displayName == 'Run build started' &&
+            buildRunningStarted.descriptor.name == 'Run build' &&
+            buildRunningStarted.descriptor.displayName == 'Run build' &&
+            buildRunningStarted.descriptor.parent == null
+        def evaluatingInitScriptsStarted = result[1]
+        evaluatingInitScriptsStarted instanceof StartEvent &&
+            evaluatingInitScriptsStarted.eventTime > 0 &&
+            evaluatingInitScriptsStarted.displayName == 'Run init scripts started' &&
+            evaluatingInitScriptsStarted.descriptor.name == 'Run init scripts' &&
+            evaluatingInitScriptsStarted.descriptor.displayName == 'Run init scripts' &&
+            evaluatingInitScriptsStarted.descriptor.parent == buildRunningStarted.descriptor
+        def evaluatingInitScriptsFinished = result[2]
+        evaluatingInitScriptsFinished instanceof FinishEvent &&
+            evaluatingInitScriptsFinished.eventTime > 0 &&
+            evaluatingInitScriptsFinished.displayName == 'Run init scripts succeeded' &&
+            evaluatingInitScriptsFinished.descriptor.name == 'Run init scripts' &&
+            evaluatingInitScriptsFinished.descriptor.displayName == 'Run init scripts' &&
+            evaluatingInitScriptsFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            evaluatingInitScriptsFinished.result instanceof SuccessResult &&
+            evaluatingInitScriptsFinished.result.startTime == evaluatingInitScriptsStarted.eventTime &&
+            evaluatingInitScriptsFinished.result.endTime == evaluatingInitScriptsFinished.eventTime
+        def evaluatingSettingsStarted = result[3]
+        evaluatingSettingsStarted instanceof StartEvent &&
+            evaluatingSettingsStarted.eventTime > 0 &&
+            evaluatingSettingsStarted.displayName == 'Load projects started' &&
+            evaluatingSettingsStarted.descriptor.name == 'Load projects' &&
+            evaluatingSettingsStarted.descriptor.displayName == 'Load projects' &&
+            evaluatingSettingsStarted.descriptor.parent == buildRunningStarted.descriptor
+        def evaluatingSettingsFinished = result[4]
+        evaluatingSettingsFinished instanceof FinishEvent &&
+            evaluatingSettingsFinished.eventTime > 0 &&
+            evaluatingSettingsFinished.displayName == 'Load projects succeeded' &&
+            evaluatingSettingsFinished.descriptor.name == 'Load projects' &&
+            evaluatingSettingsFinished.descriptor.displayName == 'Load projects' &&
+            evaluatingSettingsFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            evaluatingSettingsFinished.result instanceof SuccessResult &&
+            evaluatingSettingsFinished.result.startTime == evaluatingSettingsStarted.eventTime &&
+            evaluatingSettingsFinished.result.endTime == evaluatingSettingsFinished.eventTime
+        def configuringBuildStarted = result[5]
+        configuringBuildStarted instanceof StartEvent &&
+            configuringBuildStarted.eventTime > 0 &&
+            configuringBuildStarted.displayName == 'Configure build started' &&
+            configuringBuildStarted.descriptor.name == 'Configure build' &&
+            configuringBuildStarted.descriptor.displayName == 'Configure build' &&
+            configuringBuildStarted.descriptor.parent == buildRunningStarted.descriptor
+        def configuringBuildFinished = result[6]
+        configuringBuildFinished instanceof FinishEvent &&
+            configuringBuildFinished.eventTime > 0 &&
+            configuringBuildFinished.displayName == 'Configure build succeeded' &&
+            configuringBuildFinished.descriptor.name == 'Configure build' &&
+            configuringBuildFinished.descriptor.displayName == 'Configure build' &&
+            configuringBuildFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            configuringBuildFinished.result instanceof SuccessResult &&
+            configuringBuildFinished.result.startTime == configuringBuildStarted.eventTime &&
+            configuringBuildFinished.result.endTime == configuringBuildFinished.eventTime
+        def populatingTaskGraphStarted = result[7]
+        populatingTaskGraphStarted instanceof StartEvent &&
+            populatingTaskGraphStarted.eventTime > 0 &&
+            populatingTaskGraphStarted.displayName == 'Calculate task graph started' &&
+            populatingTaskGraphStarted.descriptor.name == 'Calculate task graph' &&
+            populatingTaskGraphStarted.descriptor.displayName == 'Calculate task graph' &&
+            populatingTaskGraphStarted.descriptor.parent == buildRunningStarted.descriptor
+        def populatingTaskGraphFinished = result[8]
+        populatingTaskGraphFinished instanceof FinishEvent &&
+            populatingTaskGraphFinished.eventTime > 0 &&
+            populatingTaskGraphFinished.displayName == 'Calculate task graph succeeded' &&
+            populatingTaskGraphFinished.descriptor.name == 'Calculate task graph' &&
+            populatingTaskGraphFinished.descriptor.displayName == 'Calculate task graph' &&
+            populatingTaskGraphFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            populatingTaskGraphFinished.result instanceof SuccessResult &&
+            populatingTaskGraphFinished.result.startTime == populatingTaskGraphStarted.eventTime &&
+            populatingTaskGraphFinished.result.endTime == populatingTaskGraphFinished.eventTime
+        def executingTasksGraphStarted = result[9]
+        executingTasksGraphStarted instanceof StartEvent &&
+            executingTasksGraphStarted.eventTime > 0 &&
+            executingTasksGraphStarted.displayName == 'Run tasks started' &&
+            executingTasksGraphStarted.descriptor.name == 'Run tasks' &&
+            executingTasksGraphStarted.descriptor.displayName == 'Run tasks' &&
+            executingTasksGraphStarted.descriptor.parent == buildRunningStarted.descriptor
+        def executingTasksFinished = result[10]
+        executingTasksFinished instanceof FinishEvent &&
+            executingTasksFinished.eventTime > 0 &&
+            executingTasksFinished.displayName == 'Run tasks succeeded' &&
+            executingTasksFinished.descriptor.name == 'Run tasks' &&
+            executingTasksFinished.descriptor.displayName == 'Run tasks' &&
+            executingTasksFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            executingTasksFinished.result instanceof SuccessResult &&
+            executingTasksFinished.result.startTime == executingTasksGraphStarted.eventTime &&
+            executingTasksFinished.result.endTime == executingTasksFinished.eventTime
+        def buildRunningFinished = result[11]
+        buildRunningFinished instanceof FinishEvent &&
+            buildRunningFinished.eventTime > 0 &&
+            buildRunningFinished.displayName == 'Run build succeeded' &&
+            buildRunningFinished.descriptor.name == 'Run build' &&
+            buildRunningFinished.descriptor.displayName == 'Run build' &&
+            buildRunningFinished.descriptor.parent == null &&
+            buildRunningFinished.result instanceof SuccessResult &&
+            buildRunningFinished.result.startTime == buildRunningStarted.eventTime &&
+            buildRunningFinished.result.endTime == buildRunningFinished.eventTime
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive build progress events for failed operations"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/test/java/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     Thread.sleep(100);  // sleep for a moment to ensure test duration is > 0 (due to limited clock resolution)
+                     throw new RuntimeException("broken", new RuntimeException("nope"));
+                }
+            }
+        """
+
+        when:
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener({ ProgressEvent event ->
+                    result << event
+                }, EnumSet.of(OperationType.GENERIC)).run()
+        }
+
+        then:
+        thrown(BuildException)
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 6 * 2          // build running, init scripts, loading, configuring, populating task graph, executing tasks
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def buildRunningStarted = result[0]
+        buildRunningStarted instanceof StartEvent &&
+            buildRunningStarted.eventTime > 0 &&
+            buildRunningStarted.displayName == 'Run build started' &&
+            buildRunningStarted.descriptor.name == 'Run build' &&
+            buildRunningStarted.descriptor.displayName == 'Run build' &&
+            buildRunningStarted.descriptor.parent == null
+        def evaluatingInitScriptsStarted = result[1]
+        evaluatingInitScriptsStarted instanceof StartEvent &&
+            evaluatingInitScriptsStarted.eventTime > 0 &&
+            evaluatingInitScriptsStarted.displayName == 'Run init scripts started' &&
+            evaluatingInitScriptsStarted.descriptor.name == 'Run init scripts' &&
+            evaluatingInitScriptsStarted.descriptor.displayName == 'Run init scripts' &&
+            evaluatingInitScriptsStarted.descriptor.parent == buildRunningStarted.descriptor
+        def evaluatingInitScriptsFinished = result[2]
+        evaluatingInitScriptsFinished instanceof FinishEvent &&
+            evaluatingInitScriptsFinished.eventTime > 0 &&
+            evaluatingInitScriptsFinished.displayName == 'Run init scripts succeeded' &&
+            evaluatingInitScriptsFinished.descriptor.name == 'Run init scripts' &&
+            evaluatingInitScriptsFinished.descriptor.displayName == 'Run init scripts' &&
+            evaluatingInitScriptsFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            evaluatingInitScriptsFinished.result instanceof SuccessResult &&
+            evaluatingInitScriptsFinished.result.startTime == evaluatingInitScriptsStarted.eventTime &&
+            evaluatingInitScriptsFinished.result.endTime == evaluatingInitScriptsFinished.eventTime
+        def evaluatingSettingsStarted = result[3]
+        evaluatingSettingsStarted instanceof StartEvent &&
+            evaluatingSettingsStarted.eventTime > 0 &&
+            evaluatingSettingsStarted.displayName == 'Load projects started' &&
+            evaluatingSettingsStarted.descriptor.name == 'Load projects' &&
+            evaluatingSettingsStarted.descriptor.displayName == 'Load projects' &&
+            evaluatingSettingsStarted.descriptor.parent == buildRunningStarted.descriptor
+        def evaluatingSettingsFinished = result[4]
+        evaluatingSettingsFinished instanceof FinishEvent &&
+            evaluatingSettingsFinished.eventTime > 0 &&
+            evaluatingSettingsFinished.displayName == 'Load projects succeeded' &&
+            evaluatingSettingsFinished.descriptor.name == 'Load projects' &&
+            evaluatingSettingsFinished.descriptor.displayName == 'Load projects' &&
+            evaluatingSettingsFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            evaluatingSettingsFinished.result instanceof SuccessResult &&
+            evaluatingSettingsFinished.result.startTime == evaluatingSettingsStarted.eventTime &&
+            evaluatingSettingsFinished.result.endTime == evaluatingSettingsFinished.eventTime
+        def configuringBuildStarted = result[5]
+        configuringBuildStarted instanceof StartEvent &&
+            configuringBuildStarted.eventTime > 0 &&
+            configuringBuildStarted.displayName == 'Configure build started' &&
+            configuringBuildStarted.descriptor.name == 'Configure build' &&
+            configuringBuildStarted.descriptor.displayName == 'Configure build' &&
+            configuringBuildStarted.descriptor.parent == buildRunningStarted.descriptor
+        def configuringBuildFinished = result[6]
+        configuringBuildFinished instanceof FinishEvent &&
+            configuringBuildFinished.eventTime > 0 &&
+            configuringBuildFinished.displayName == 'Configure build succeeded' &&
+            configuringBuildFinished.descriptor.name == 'Configure build' &&
+            configuringBuildFinished.descriptor.displayName == 'Configure build' &&
+            configuringBuildFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            configuringBuildFinished.result instanceof SuccessResult &&
+            configuringBuildFinished.result.startTime == configuringBuildStarted.eventTime &&
+            configuringBuildFinished.result.endTime == configuringBuildFinished.eventTime
+        def populatingTaskGraphStarted = result[7]
+        populatingTaskGraphStarted instanceof StartEvent &&
+            populatingTaskGraphStarted.eventTime > 0 &&
+            populatingTaskGraphStarted.displayName == 'Calculate task graph started' &&
+            populatingTaskGraphStarted.descriptor.name == 'Calculate task graph' &&
+            populatingTaskGraphStarted.descriptor.displayName == 'Calculate task graph' &&
+            populatingTaskGraphStarted.descriptor.parent == buildRunningStarted.descriptor
+        def populatingTaskGraphFinished = result[8]
+        populatingTaskGraphFinished instanceof FinishEvent &&
+            populatingTaskGraphFinished.eventTime > 0 &&
+            populatingTaskGraphFinished.displayName == 'Calculate task graph succeeded' &&
+            populatingTaskGraphFinished.descriptor.name == 'Calculate task graph' &&
+            populatingTaskGraphFinished.descriptor.displayName == 'Calculate task graph' &&
+            populatingTaskGraphFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            populatingTaskGraphFinished.result instanceof SuccessResult &&
+            populatingTaskGraphFinished.result.startTime == populatingTaskGraphStarted.eventTime &&
+            populatingTaskGraphFinished.result.endTime == populatingTaskGraphFinished.eventTime
+        def executingTasksGraphStarted = result[9]
+        executingTasksGraphStarted instanceof StartEvent &&
+            executingTasksGraphStarted.eventTime > 0 &&
+            executingTasksGraphStarted.displayName == 'Run tasks started' &&
+            executingTasksGraphStarted.descriptor.name == 'Run tasks' &&
+            executingTasksGraphStarted.descriptor.displayName == 'Run tasks' &&
+            executingTasksGraphStarted.descriptor.parent == buildRunningStarted.descriptor
+        def executingTasksFinished = result[10]
+        executingTasksFinished instanceof FinishEvent &&
+            executingTasksFinished.eventTime > 0 &&
+            executingTasksFinished.displayName == 'Run tasks failed' &&
+            executingTasksFinished.descriptor.name == 'Run tasks' &&
+            executingTasksFinished.descriptor.displayName == 'Run tasks' &&
+            executingTasksFinished.descriptor.parent == buildRunningStarted.descriptor &&
+            executingTasksFinished.result instanceof FailureResult &&
+            executingTasksFinished.result.startTime == executingTasksGraphStarted.eventTime &&
+            executingTasksFinished.result.endTime == executingTasksFinished.eventTime &&
+            executingTasksFinished.result.failures.size() == 1
+        def buildRunningFinished = result[11]
+        buildRunningFinished instanceof FinishEvent &&
+            buildRunningFinished.eventTime > 0 &&
+            buildRunningFinished.displayName == 'Run build failed' &&
+            buildRunningFinished.descriptor.name == 'Run build' &&
+            buildRunningFinished.descriptor.displayName == 'Run build' &&
+            buildRunningFinished.descriptor.parent == null &&
+            buildRunningFinished.result instanceof FailureResult &&
+            buildRunningFinished.result.startTime == buildRunningStarted.eventTime &&
+            buildRunningFinished.result.endTime == buildRunningFinished.eventTime &&
+            buildRunningFinished.result.failures.size() == 1
+    }
+
+    @TargetGradleVersion('>=2.5')
+    @ToolingApiVersion('>=2.5')
+    @NotYetImplemented
+    def "should receive build events from GradleBuild"() {
+        buildFile << """task innerBuild(type:GradleBuild) {
+            buildFile = file('other.gradle')
+            tasks = ['innerTask']
+            startParameter.searchUpwards = false
+        }"""
+        file("other.gradle") << """
+            task innerTask()
+        """
+
+        when:
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection { ProjectConnection connection ->
+            connection.newBuild().forTasks('innerBuild').addProgressListener({ ProgressEvent event ->
+                result << event
+            }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() % 2 == 0       // same number of start events as finish events
+        result.size() == 7 * 2 * 2   // life-cycle for both inner and outer build
+    }
+
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            compileJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/main/java/example/MyClass.java") << """
+            package example;
+            public class MyClass {
+                public void foo() throws Exception {
+                    Thread.sleep(100);
+                }
+            }
+        """
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCancellationCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCancellationCrossVersionSpec.groovy
new file mode 100644
index 0000000..55cb6fd
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCancellationCrossVersionSpec.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import org.gradle.integtests.tooling.fixture.ContinuousBuildToolingApiSpecification
+import org.gradle.test.fixtures.server.http.CyclicBarrierHttpServer
+import org.gradle.tooling.BuildCancelledException
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+
+class ContinuousBuildCancellationCrossVersionSpec extends ContinuousBuildToolingApiSpecification {
+
+    @Rule
+    CyclicBarrierHttpServer cyclicBarrierHttpServer = new CyclicBarrierHttpServer()
+
+    def "client can cancel during execution of a continuous build"() {
+        given:
+        buildFile << """
+            gradle.taskGraph.whenReady { new URL('${cyclicBarrierHttpServer.uri}').text }
+        """
+
+        when:
+        runBuild {
+            cyclicBarrierHttpServer.waitFor()
+            cancel()
+            cyclicBarrierHttpServer.release()
+        }
+
+        then:
+        if (toolingApiVersion.equals(GradleVersion.version("2.1"))) {
+            assert buildResult.failure instanceof GradleConnectionException
+        } else {
+            assert buildResult.failure instanceof BuildCancelledException
+        }
+        !stdout.toString().contains(WAITING_MESSAGE)
+    }
+
+
+    def "logging does not include message to use ctrl-d to exit"() {
+        when:
+        runBuild {
+            succeeds()
+            cancel()
+        }
+
+        then:
+        !result.output.contains("ctrl-d")
+        result.output.contains(WAITING_MESSAGE)
+    }
+
+    def "after cancelling a continuous build, we can subsequently run another"() {
+        when:
+        withConnection {
+            runBuild {
+                succeeds()
+                cancel()
+            }
+            assert !buildResult.failure
+            runBuild {
+                succeeds()
+                cancel()
+            }
+        }
+
+        then:
+        !buildResult.failure
+    }
+
+    def "can cancel in subsequent wait period"() {
+        when:
+        runBuild {
+            succeeds()
+            sourceDir.file("Thing.java") << "class Thing {}"
+            succeeds()
+            cancel()
+        }
+
+        then:
+        !buildResult.failure
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCrossVersionSpec.groovy
new file mode 100644
index 0000000..a3bdae0
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildCrossVersionSpec.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import org.gradle.integtests.tooling.fixture.ContinuousBuildToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.model.GradleProject
+
+class ContinuousBuildCrossVersionSpec extends ContinuousBuildToolingApiSpecification {
+
+    def "can run continuous build with tooling api"() {
+        when:
+        def javaSrcFile = sourceDir.file("Thing.java") << 'public class Thing {}'
+
+        then:
+        runBuild {
+            succeeds()
+            javaSrcFile.text = 'public class Thing { public static final int FOO=1; }'
+            succeeds()
+        }
+    }
+
+    def "can recover from failures"() {
+        when:
+        def javaSrcFile = sourceDir.file("Thing.java") << 'public class Thing {}'
+
+        then:
+        runBuild {
+            succeeds()
+            javaSrcFile.text = 'public class Thing { *******'
+            fails()
+            javaSrcFile.text = 'public class Thing {} '
+            succeeds()
+        }
+    }
+
+    def "client can request continuous mode when building a model, but request is effectively ignored"() {
+        when:
+        // take care to not use runBuild which implicitly calls cancel
+        // we want to make sure it doesn't need cancellation
+        GradleProject project = withConnection { ProjectConnection connection ->
+            connection.model(GradleProject.class)
+                .withArguments("--continuous")
+                .setStandardOutput(stdout)
+                .setStandardError(stderr)
+                .get()
+        }
+
+        then:
+        project != null
+        def logOutput = stdout.toString()
+        !logOutput.contains("Continuous build is an incubating feature.")
+        !logOutput.contains(WAITING_MESSAGE)
+        !logOutput.contains("Exiting continuous build as no executed tasks declared file system inputs.")
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildProgressEventsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildProgressEventsCrossVersionSpec.groovy
new file mode 100644
index 0000000..342cc60
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousBuildProgressEventsCrossVersionSpec.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import org.gradle.integtests.tooling.fixture.ContinuousBuildToolingApiSpecification
+import org.gradle.integtests.tooling.fixture.ToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiVersions
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.events.FinishEvent
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.ProgressListener
+
+ at ToolingApiVersion(ToolingApiVersions.SUPPORTS_RICH_PROGRESS_EVENTS)
+class ContinuousBuildProgressEventsCrossVersionSpec extends ContinuousBuildToolingApiSpecification {
+
+    List<ProgressEvent> events = []
+
+    void customizeLauncher(BuildLauncher launcher) {
+        launcher.addProgressListener({ events << it } as ProgressListener)
+    }
+
+    def "client can receive appropriate logging and progress events for subsequent builds"() {
+        when:
+        def javaSrcFile = sourceDir.file("Thing.java") << 'public class Thing {}'
+
+        then:
+        runBuild {
+            succeeds()
+            receivedBuildEvents()
+            javaSrcFile.setText('public class Thing { public static final int FOO = 1; }')
+            succeeds()
+            receivedBuildEvents()
+            cancel()
+        }
+    }
+
+    void receivedBuildEvents() {
+        assert !events.isEmpty()
+        assert events.last() instanceof FinishEvent
+        events.clear()
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedJavaVersionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedJavaVersionCrossVersionSpec.groovy
new file mode 100644
index 0000000..0cc4409
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedJavaVersionCrossVersionSpec.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.executer.GradleVersions
+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.fixture.ToolingApiVersions
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.util.Requires
+
+ at ToolingApiVersion(ToolingApiVersions.SUPPORTS_CANCELLATION)
+ at TargetGradleVersion(GradleVersions.SUPPORTS_CONTINUOUS)
+ at Requires(adhoc = { AvailableJavaHomes.jdk6 })
+class ContinuousUnsupportedJavaVersionCrossVersionSpec extends ToolingApiSpecification {
+
+    def "client receives error on unsupported platform"() {
+        given:
+        toolingApi.requireIsolatedDaemons()
+        buildFile.text = "apply plugin: 'java'"
+
+        when:
+        withConnection {
+            newBuild()
+                .setJavaHome(AvailableJavaHomes.jdk6.javaHome)
+                .withArguments("--continuous")
+                .forTasks("build")
+                .run()
+        }
+
+        then:
+        def e = thrown(GradleConnectionException)
+        e.message.startsWith("Could not execute build using")
+        e.cause.message == 'Continuous build requires Java 7 or later.'
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedToolingApiVersionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedToolingApiVersionCrossVersionSpec.groovy
new file mode 100644
index 0000000..0dc11d2
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ContinuousUnsupportedToolingApiVersionCrossVersionSpec.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import org.gradle.integtests.fixtures.executer.GradleVersions
+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.fixture.ToolingApiVersions
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
+import spock.lang.Timeout
+
+class ContinuousUnsupportedToolingApiVersionCrossVersionSpec extends ToolingApiSpecification {
+    @Timeout(120)
+    @ToolingApiVersion(ToolingApiVersions.PRE_CANCELLATION)
+    @TargetGradleVersion(GradleVersions.SUPPORTS_CONTINUOUS)
+    def "client receives appropriate error if continuous build attempted using client that does not support cancellation"() {
+        when:
+        buildFile.text = "apply plugin: 'java'"
+        withConnection {
+            newBuild()
+                .withArguments("--continuous")
+                .forTasks("build")
+                .run()
+        }
+
+        then:
+        def e = thrown(GradleConnectionException)
+        e.message.startsWith("Could not execute build using")
+        e.cause.message == "Continuous build requires Tooling API client version 2.1 or later."
+    }
+
+    @Timeout(120)
+    @ToolingApiVersion(ToolingApiVersions.SUPPORTS_CANCELLATION)
+    @TargetGradleVersion(GradleVersions.PRE_CONTINUOUS)
+    def "client receives appropriate error target Gradle version does not support cancellation"() {
+        when:
+        buildFile.text = "apply plugin: 'java'"
+        withConnection {
+            newBuild()
+                .withArguments("--continuous")
+                .forTasks("build")
+                .run()
+        }
+
+        then:
+        def e = thrown(UnsupportedBuildArgumentException)
+        e.message.startsWith("Could not execute build using")
+        e.cause.message.contains("Unknown command-line option '--continuous'.")
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/GradleTaskGetGroupCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/GradleTaskGetGroupCrossVersionSpec.groovy
new file mode 100644
index 0000000..5f9de97
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/GradleTaskGetGroupCrossVersionSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+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.gradle.BuildInvocations
+
+ at ToolingApiVersion(">=2.5")
+ at TargetGradleVersion(">=2.5")
+class GradleTaskGetGroupCrossVersionSpec extends ToolingApiSpecification {
+
+    def "provide getGroup on Task using GradleProject"() {
+        file("build.gradle") << '''
+task test1(group:'task group 1')
+task test2(group:'task group 2')
+'''
+
+        when:
+        def gradleProject = withConnection { ProjectConnection connection ->
+            connection.getModel(GradleProject)
+        }
+
+        then:
+        gradleProject != null
+        gradleProject.tasks.findAll { it.name.startsWith('test') }.each {
+            assert it.group == "task group ${it.name-'test'}"
+        }
+    }
+
+    def "provide getGroup on Task using BuildInvocations"() {
+        file("build.gradle") << '''
+task test1(group:'task group 1')
+task test2(group:'task group 2')
+'''
+
+        when:
+        def buildInvocations = withConnection { ProjectConnection connection ->
+            connection.getModel(BuildInvocations)
+        }
+
+        then:
+        buildInvocations != null
+        buildInvocations.tasks.findAll { it.name.startsWith('test') }.each {
+            assert it.group == "task group ${it.name-'test'}"
+        }
+    }
+
+    def "provide getGroup on Task using GradleProject shouldn't fail if group is null"() {
+        file("build.gradle") << '''
+task test1()
+task test2()
+'''
+
+        when:
+        def gradleProject = withConnection { ProjectConnection connection ->
+            connection.getModel(GradleProject)
+        }
+
+        then:
+        gradleProject != null
+        gradleProject.tasks.findAll { it.name.startsWith('test') }.each {
+            assert it.group == null
+        }
+    }
+
+    def "provide getGroup on Task using BuildInvocations shouldn't fail if group is null"() {
+        file("build.gradle") << '''
+task test1()
+task test2()
+'''
+
+        when:
+        def buildInvocations = withConnection { ProjectConnection connection ->
+            connection.getModel(BuildInvocations)
+        }
+
+        then:
+        buildInvocations != null
+        buildInvocations.tasks.findAll { it.name.startsWith('test') }.each {
+            assert it.group == null
+        }
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/NullAction.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/NullAction.java
new file mode 100644
index 0000000..42f1548
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/NullAction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25;
+
+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/r25/ProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ProgressCrossVersionSpec.groovy
new file mode 100644
index 0000000..6c68cc2
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ProgressCrossVersionSpec.groovy
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+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.events.OperationType
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.internal.DefaultFinishEvent
+import org.gradle.tooling.events.internal.DefaultStartEvent
+import org.gradle.tooling.events.task.TaskFinishEvent
+import org.gradle.tooling.events.task.TaskProgressEvent
+import org.gradle.tooling.events.task.TaskStartEvent
+import org.gradle.tooling.events.test.TestFinishEvent
+import org.gradle.tooling.events.test.TestProgressEvent
+import org.gradle.tooling.events.test.TestStartEvent
+import org.gradle.tooling.model.gradle.BuildInvocations
+
+ at ToolingApiVersion(">=2.5")
+ at TargetGradleVersion(">=2.5")
+class ProgressCrossVersionSpec extends ToolingApiSpecification {
+
+    def "receive progress events when requesting a model"() {
+        given:
+        goodCode()
+
+        when: "asking for a model and specifying some task(s) to run first"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.model(BuildInvocations).forTasks('assemble').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }).get()
+        }
+
+        then: "progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    def "receive progress events when launching a build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }).run()
+        }
+
+        then: "progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    def "receive progress events when running a build action"() {
+        given:
+        goodCode()
+
+        when: "running a build action"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.action(new NullAction()).addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }).run()
+        }
+
+        then: "progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    def "register for all progress events at once"() {
+        given:
+        goodCode()
+
+        when: "registering for all progress event types"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }, EnumSet.allOf(OperationType)).run()
+        }
+
+        then: "all progress events must be forwarded to the attached listener"
+        result.size() > 0
+        result.findAll { it instanceof TestProgressEvent }.size() > 0
+        result.findAll { it instanceof TaskProgressEvent }.size() > 0
+        result.findAll { it.class == DefaultStartEvent || it.class == DefaultFinishEvent }.size() > 0
+        result.findIndexOf { it.class == DefaultStartEvent } < result.findIndexOf { it instanceof TaskStartEvent }
+        result.findIndexOf { it instanceof TaskStartEvent } < result.findIndexOf { it instanceof TestStartEvent }
+        result.findLastIndexOf { it instanceof TaskFinishEvent } > result.findLastIndexOf { it instanceof TestFinishEvent }
+        result.findLastIndexOf { it.class == DefaultFinishEvent } > result.findLastIndexOf { it instanceof TaskFinishEvent }
+    }
+
+    def "register for subset of progress events at once"() {
+        given:
+        goodCode()
+
+        when: "registering for subset of progress event types"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then: "only the matching progress events must be forwarded to the attached listener"
+        result.size() > 0
+        result.findAll { it instanceof TestProgressEvent }.size() > 0
+        result.findAll { it instanceof TaskProgressEvent }.isEmpty()
+        result.findAll { it.class == DefaultStartEvent || it.class == DefaultFinishEvent }.isEmpty()
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion("=2.4")
+    def "register for all progress events when provider version only knows how to send test progress events"() {
+        given:
+        goodCode()
+
+        when: "registering for all progress event types but provider only knows how to send test progress events"
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }, EnumSet.allOf(OperationType)).run()
+        }
+
+        then: "only test progress events must be forwarded to the attached listener"
+        result.size() > 0
+        result.findAll { it instanceof TestProgressEvent }.size() > 0
+        result.findAll { it instanceof TaskProgressEvent }.isEmpty()
+        result.findAll { it.class == DefaultStartEvent || it.class == DefaultFinishEvent }.isEmpty()
+    }
+
+    def "when listening to all progress events they are all in a hierarchy with a single root node"() {
+        given:
+        goodCode()
+
+        when: 'listening to progress events'
+        List<ProgressEvent> result = new ArrayList<ProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('build').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << event
+                    }
+                }, EnumSet.allOf(OperationType.class)).run()
+        }
+
+        then: 'all events are in a hierarchy with a single root node'
+        !result.isEmpty()
+        def rootNodes = result.findAll { it.descriptor.parent == null }
+        rootNodes.size() == 2
+        rootNodes.each { it.class == DefaultStartEvent || it.class == DefaultFinishEvent }
+    }
+
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true
+        """
+
+        file("src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TaskProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TaskProgressCrossVersionSpec.groovy
new file mode 100644
index 0000000..2b04474
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TaskProgressCrossVersionSpec.groovy
@@ -0,0 +1,594 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import groovy.transform.NotYetImplemented
+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.ListenerFailedException
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.events.OperationType
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.task.*
+import org.gradle.tooling.model.gradle.BuildInvocations
+
+class TaskProgressCrossVersionSpec extends ToolingApiSpecification {
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=1.0-milestone-8 <2.5")
+    def "ignores listeners when Gradle version does not generate task events"() {
+        given:
+        goodCode()
+
+        when:
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({
+                    throw new RuntimeException()
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events when requesting a model"() {
+        given:
+        goodCode()
+
+        when: "asking for a model and specifying some task(s) to run first"
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.model(BuildInvocations).forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).get()
+        }
+
+        then: "task progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events when launching a build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then: "task progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive current task progress event even if one of multiple task listeners throws an exception"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<TaskProgressEvent> resultsOfFirstListener = new ArrayList<TaskProgressEvent>()
+        List<TaskProgressEvent> resultsOfLastListener = new ArrayList<TaskProgressEvent>()
+        def stdout = new ByteArrayOutputStream()
+        def failure = new IllegalStateException("Throwing an exception on purpose")
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    resultsOfFirstListener << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).addProgressListener({ ProgressEvent event ->
+                    throw failure
+                }, EnumSet.of(OperationType.TASK)).addProgressListener({ ProgressEvent event ->
+                    resultsOfLastListener << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).setStandardOutput(stdout).run()
+        }
+
+        then: "listener exception is wrapped"
+        ListenerFailedException ex = thrown()
+        ex.message.startsWith("Could not execute build using")
+        ex.causes == [failure]
+
+        and: "expected events received"
+        resultsOfFirstListener.size() == 1
+        resultsOfLastListener.size() == 1
+
+        and: "build execution is successful"
+        stdout.toString().contains("BUILD SUCCESSFUL")
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events for all tasks"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            compileJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+            classes.enabled = false
+
+            task failingTask() << { throw new RuntimeException() }
+
+        """
+
+        file("src/main/java/example/MyClass.java") << """
+            package example;
+            public class MyClass {
+                public void foo() throws Exception {
+                    Thread.sleep(100);
+                }
+            }
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('classes').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 6              // compileJava, processResources, classes
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def compileJavaStartEvent = result[0]
+        compileJavaStartEvent instanceof TaskStartEvent &&
+            compileJavaStartEvent.eventTime > 0 &&
+            compileJavaStartEvent.displayName == "Task :compileJava started" &&
+            compileJavaStartEvent.descriptor.name == ':compileJava' &&
+            compileJavaStartEvent.descriptor.displayName == 'Task :compileJava' &&
+            compileJavaStartEvent.descriptor.taskPath == ':compileJava' &&
+            compileJavaStartEvent.descriptor.parent == null
+        def compileJavaFinishEvent = result[1]
+        compileJavaFinishEvent instanceof TaskFinishEvent &&
+            compileJavaFinishEvent.eventTime > 0 &&
+            compileJavaFinishEvent.displayName == "Task :compileJava succeeded" &&
+            compileJavaFinishEvent.descriptor.name == ':compileJava' &&
+            compileJavaFinishEvent.descriptor.displayName == 'Task :compileJava' &&
+            compileJavaFinishEvent.descriptor.taskPath == ':compileJava' &&
+            compileJavaFinishEvent.descriptor.parent == null &&
+            compileJavaFinishEvent.result instanceof TaskSuccessResult &&
+            compileJavaFinishEvent.result.startTime == compileJavaStartEvent.eventTime &&
+            compileJavaFinishEvent.result.endTime == compileJavaFinishEvent.eventTime &&
+            !compileJavaFinishEvent.result.isUpToDate()
+        def processResourcesStartEvent = result[2]
+        processResourcesStartEvent instanceof TaskStartEvent &&
+            processResourcesStartEvent.eventTime > 0 &&
+            processResourcesStartEvent.displayName == "Task :processResources started" &&
+            processResourcesStartEvent.descriptor.name == ':processResources' &&
+            processResourcesStartEvent.descriptor.displayName == 'Task :processResources' &&
+            processResourcesStartEvent.descriptor.taskPath == ':processResources' &&
+            processResourcesStartEvent.descriptor.parent == null
+        def processResourcesFinishEvent = result[3]
+        processResourcesFinishEvent instanceof TaskFinishEvent &&
+            processResourcesFinishEvent.eventTime > 0 &&
+            processResourcesFinishEvent.displayName == "Task :processResources succeeded" &&
+            processResourcesFinishEvent.descriptor.name == ':processResources' &&
+            processResourcesFinishEvent.descriptor.displayName == 'Task :processResources' &&
+            processResourcesFinishEvent.descriptor.taskPath == ':processResources' &&
+            processResourcesFinishEvent.descriptor.parent == null &&
+            processResourcesFinishEvent.result instanceof TaskSuccessResult &&
+            processResourcesFinishEvent.result.startTime == processResourcesStartEvent.eventTime &&
+            processResourcesFinishEvent.result.endTime == processResourcesFinishEvent.eventTime &&
+            processResourcesFinishEvent.result.upToDate
+        def classesStartEvent = result[4]
+        classesStartEvent instanceof TaskStartEvent &&
+            classesStartEvent.eventTime > 0 &&
+            classesStartEvent.displayName == "Task :classes started" &&
+            classesStartEvent.descriptor.name == ':classes' &&
+            classesStartEvent.descriptor.displayName == 'Task :classes' &&
+            classesStartEvent.descriptor.taskPath == ':classes' &&
+            classesStartEvent.descriptor.parent == null
+        def classesFinishEvent = result[5]
+        classesFinishEvent instanceof TaskFinishEvent &&
+            classesFinishEvent.eventTime > 0 &&
+            classesFinishEvent.displayName == "Task :classes skipped" &&
+            classesFinishEvent.descriptor.name == ':classes' &&
+            classesFinishEvent.descriptor.displayName == 'Task :classes' &&
+            classesFinishEvent.descriptor.taskPath == ':classes' &&
+            classesFinishEvent.descriptor.parent == null &&
+            classesFinishEvent.result instanceof TaskSkippedResult &&
+            classesFinishEvent.result.startTime == classesStartEvent.eventTime &&
+            classesFinishEvent.result.endTime == classesFinishEvent.eventTime &&
+            classesFinishEvent.result.skipMessage == 'SKIPPED'
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events for successful tasks"() {
+        given:
+        goodCode()
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() == 2 * tasks.size()
+        assertOrderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            compileJava     : ['started', 'succeeded'],
+            processResources: ['started', 'up-to-date'],
+            classes         : ['started', 'succeeded'],
+            jar             : ['started', 'succeeded'],
+            assemble        : ['started', 'succeeded']
+        ]
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events for failed tasks"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/test/java/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     Thread.sleep(100);  // sleep for a moment to ensure test duration is > 0 (due to limited clock resolution)
+                     throw new RuntimeException("broken", new RuntimeException("nope"));
+                }
+            }
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        BuildException ex = thrown()
+        ex.cause.cause.message =~ /Execution failed for task ':test'/
+        result.size() == 2 * tasks.size()
+        assertOrderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            compileJava         : ['started', 'up-to-date'],
+            processResources    : ['started', 'up-to-date'],
+            classes             : ['started', 'up-to-date'],
+            compileTestJava     : ['started', 'succeeded'],
+            processTestResources: ['started', 'up-to-date'],
+            testClasses         : ['started', 'succeeded'],
+            test                : ['started', 'failed']
+        ]
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events for disabled tasks"() {
+        buildFile << """
+            apply plugin: 'java'
+            compileJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+            assemble.enabled = false
+        """
+
+        file("src/main/java/example/MyClass.java") << """
+            package example;
+            public class MyClass {
+                public void foo() throws Exception {
+                    Thread.sleep(100);
+                }
+            }
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() == 2 * tasks.size()
+        assertOrderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            compileJava     : ['started', 'succeeded'],
+            processResources: ['started', 'up-to-date'],
+            classes         : ['started', 'succeeded'],
+            jar             : ['started', 'succeeded'],
+            assemble        : ['started', 'skipped']
+        ]
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "receive task progress events when tasks are executed in parallel"() {
+        given:
+        buildFile << """
+            @ParallelizableTask
+            class ParTask extends DefaultTask {
+                @TaskAction zzz() { Thread.sleep(1000) }
+            }
+
+            task para1(type:ParTask)
+            task para2(type:ParTask)
+            task parallelSleep(dependsOn:[para1,para2])
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().withArguments("-Dorg.gradle.parallel.intra=true", '--parallel', '--max-workers=2').forTasks('parallelSleep').addProgressListener({ ProgressEvent event ->
+                    result << (event as TaskProgressEvent)
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() == 2 * tasks.size()
+        assertUnorderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            para1        : ['started', 'succeeded'],
+            para2        : ['started', 'succeeded'],
+            parallelSleep: ['started', 'succeeded']
+        ]
+    }
+
+    @TargetGradleVersion('>=2.5')
+    @ToolingApiVersion('>=2.5')
+    @NotYetImplemented
+    def "should receive task events from buildSrc"() {
+        buildFile << """
+            apply plugin: 'java'
+            task dummy()
+        """
+
+        file("buildSrc/build.gradle") << """
+            task taskInBuildSrc()
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection { ProjectConnection connection ->
+            connection.newBuild().forTasks('dummy').addProgressListener({ ProgressEvent event ->
+                result << (event as TaskProgressEvent)
+            }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() == 2 * tasks.size()
+        assertOrderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            ':buildSrc:clean'               : ['started', 'up-to-date'],
+            ':buildSrc:compileJava'         : ['started', 'up-to-date'],
+            ':buildSrc:compileGroovy'       : ['started', 'up-to-date'],
+            ':buildSrc:processResources'    : ['started', 'up-to-date'],
+            ':buildSrc:classes'             : ['started', 'up-to-date'],
+            ':buildSrc:jar'                 : ['started', 'succeeded'],
+            ':buildSrc:assemble'            : ['started', 'succeeded'],
+            ':buildSrc:compileTestJava'     : ['started', 'up-to-date'],
+            ':buildSrc:compileTestGroovy'   : ['started', 'up-to-date'],
+            ':buildSrc:processTestResources': ['started', 'up-to-date'],
+            ':buildSrc:testClasses'         : ['started', 'up-to-date'],
+            ':buildSrc:test'                : ['started', 'up-to-date'],
+            ':buildSrc:build'               : ['started', 'up-to-date'],
+            ':dummy'                        : ['started', 'up-to-date']
+        ]
+    }
+
+    @TargetGradleVersion('>=2.5')
+    @ToolingApiVersion('>=2.5')
+    @NotYetImplemented
+    def "should receive task events from GradleBuild"() {
+        buildFile << """
+            task innerBuild(type:GradleBuild) {
+                buildFile = file('other.gradle')
+                tasks = ['innerTask']
+                startParameter.searchUpwards = false
+            }
+        """
+
+        file("other.gradle") << """
+            task innerTask()
+        """
+
+        when:
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection { ProjectConnection connection ->
+            connection.newBuild().forTasks('innerBuild').addProgressListener({ ProgressEvent event ->
+                result << (event as TaskProgressEvent)
+            }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then:
+        result.size() == 2 * tasks.size()
+        assertUnorderedEvents(result, tasks)
+
+        where:
+        tasks = [
+            ':innerTask' : ['started', 'up-to-date'],
+            ':innerBuild': ['started', 'succeeded']
+        ]
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "task operations have root build operation as parent iff build listener is attached"() {
+        given:
+        goodCode()
+
+        when: 'listening to task progress events and build operation listener is attached'
+        List<TaskProgressEvent> result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('assemble').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        if (event instanceof TaskProgressEvent) {
+                            result << (event as TaskProgressEvent)
+                        }
+                    }
+                }, EnumSet.of(OperationType.GENERIC, OperationType.TASK)).run()
+        }
+
+        then: 'the parent of the task events is the root build operation'
+        !result.isEmpty()
+        result.each { def event ->
+            assert event.descriptor.parent
+            assert event.descriptor.parent.parent
+            assert event.descriptor.parent.parent.parent == null
+        }
+
+        when: 'listening to task progress events when no build operation listener is attached'
+        result = new ArrayList<TaskProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().withArguments('--rerun-tasks').forTasks('assemble').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TaskProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TASK)).run()
+        }
+
+        then: 'the parent of the task events is null'
+        !result.isEmpty()
+        result.each { def event ->
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            compileJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/main/java/example/MyClass.java") << """
+            package example;
+            public class MyClass {
+                public void foo() throws Exception {
+                    Thread.sleep(100);
+                }
+            }
+        """
+    }
+
+    private static void assertUnorderedEvents(List<TaskProgressEvent> events, Map<String, List<String>> tasks) {
+        assertEvents(events, tasks, false)
+    }
+
+    private static void assertOrderedEvents(List<TaskProgressEvent> events, Map<String, List<String>> tasks) {
+        assertEvents(events, tasks, true)
+    }
+
+    private static void assertEvents(List<TaskProgressEvent> events, Map<String, List<String>> tasks, boolean ordered) {
+        int idx = 0
+        long oldEndTime = 0
+        if (!ordered) {
+            // reorder events to make sure we can test that all events have their expected
+            // outputs
+            events = events.sort { it.descriptor.taskPath }
+        }
+
+        tasks.each { path, List<String> states ->
+            states.each { state ->
+                def event = events[idx]
+                assert event.eventTime > 0
+                if (ordered) {
+                    assert event.eventTime >= oldEndTime
+                }
+                assert event.descriptor.taskPath == ":$path"
+                assert event.descriptor.name == ":$path"
+                switch (state) {
+                    case 'started':
+                        assert event instanceof TaskStartEvent
+                        break
+                    case 'up-to-date':
+                        assert event instanceof TaskFinishEvent
+                        assert event.result instanceof TaskSuccessResult
+                        if (ordered) {
+                            assert event.result.startTime >= oldEndTime
+                        }
+                        assert event.result.endTime >= event.result.startTime
+                        assert event.result.isUpToDate()
+                        break
+                    case 'skipped':
+                        assert event instanceof TaskFinishEvent
+                        assert event.result instanceof TaskSkippedResult
+                        if (ordered) {
+                            assert event.result.startTime >= oldEndTime
+                        }
+                        assert event.result.endTime >= event.result.startTime
+                        assert event.result.skipMessage == 'SKIPPED'
+                        break
+                    case 'succeeded':
+                        assert event instanceof TaskFinishEvent
+                        assert event.result instanceof TaskSuccessResult
+                        if (ordered) {
+                            assert event.result.startTime >= oldEndTime
+                        }
+                        assert event.result.endTime >= event.result.startTime
+                        break
+                    case 'failed':
+                        assert event instanceof TaskFinishEvent
+                        assert event.result instanceof TaskFailureResult
+                        if (ordered) {
+                            assert event.result.startTime >= oldEndTime
+                        }
+                        assert event.result.endTime >= event.result.startTime
+                        break
+                    default:
+                        throw new RuntimeException("Illegal state [$state]. Please check your test.")
+                }
+                oldEndTime = event.eventTime
+                idx++
+            }
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy
new file mode 100644
index 0000000..efe2e2e
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy
@@ -0,0 +1,770 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+import groovy.transform.NotYetImplemented
+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.file.TestFile
+import org.gradle.tooling.ListenerFailedException
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.events.OperationType
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.task.TaskOperationDescriptor
+import org.gradle.tooling.events.test.*
+import org.gradle.tooling.model.gradle.BuildInvocations
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import java.util.concurrent.ConcurrentLinkedQueue
+
+class TestProgressCrossVersionSpec extends ToolingApiSpecification {
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=1.0-milestone-8 <2.4")
+    def "ignores listeners when Gradle version does not generate test events"() {
+        given:
+        goodCode()
+
+        when:
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener({
+                    throw new RuntimeException()
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive test progress events when requesting a model"() {
+        given:
+        goodCode()
+
+        when: "asking for a model and specifying some test task(s) to run first"
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.model(BuildInvocations.class).forTasks('test').addProgressListener({ ProgressEvent event ->
+                    result << (event as TestProgressEvent)
+                }, EnumSet.of(OperationType.TEST)).get()
+        }
+
+        then: "test progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive test progress events when launching a build"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then: "test progress events must be forwarded to the attached listeners"
+        result.size() > 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive current test progress event even if one of multiple test listeners throws an exception"() {
+        given:
+        goodCode()
+
+        when: "launching a build"
+        List<TestProgressEvent> resultsOfFirstListener = new ArrayList<TestProgressEvent>()
+        List<TestProgressEvent> resultsOfLastListener = new ArrayList<TestProgressEvent>()
+        def stdout = new ByteArrayOutputStream()
+        def failure = new IllegalStateException("Throwing an exception on purpose")
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        resultsOfFirstListener << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        throw failure
+                    }
+                }, EnumSet.of(OperationType.TEST)).addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        resultsOfLastListener << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).setStandardOutput(stdout).run()
+        }
+
+        then: "listener exception is wrapped"
+        ListenerFailedException ex = thrown()
+        ex.message.startsWith("Could not execute build using")
+        ex.causes == [failure]
+
+        and: "expected events received"
+        resultsOfFirstListener.size() == 1
+        resultsOfLastListener.size() == 1
+
+        and: "build execution is successful"
+        stdout.toString().contains("BUILD SUCCESSFUL")
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive test progress events for successful test run"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     Thread.sleep(100);  // sleep for a moment to ensure test duration is > 0 (due to limited clock resolution)
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+
+        when:
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 8              // root suite, test process suite, test class suite, test method (each with a start and finish event)
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def rootStartedEvent = result[0]
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
+        def testProcessStartedEvent = result[1]
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        def testClassStartedEvent = result[2]
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        def testStartedEvent = result[3]
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        def testSucceededEvent = result[4]
+        testSucceededEvent instanceof TestFinishEvent &&
+            testSucceededEvent.eventTime >= testSucceededEvent.result.endTime &&
+            testSucceededEvent.displayName == "Test foo(example.MyTest) succeeded" &&
+            testSucceededEvent.descriptor == testStartedEvent.descriptor &&
+            testSucceededEvent.result instanceof TestSuccessResult &&
+            testSucceededEvent.result.startTime == testStartedEvent.eventTime &&
+            testSucceededEvent.result.endTime > testSucceededEvent.result.startTime &&
+            testSucceededEvent.result.endTime == testSucceededEvent.eventTime
+        def testClassSucceededEvent = result[5]
+        testClassSucceededEvent instanceof TestFinishEvent &&
+            testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
+            testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
+            testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassSucceededEvent.result instanceof TestSuccessResult &&
+            testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassSucceededEvent.result.endTime > testClassSucceededEvent.result.startTime &&
+            testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
+        def testProcessSucceededEvent = result[6]
+        testProcessSucceededEvent instanceof TestFinishEvent &&
+            testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
+            testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
+            testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessSucceededEvent.result instanceof TestSuccessResult &&
+            testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessSucceededEvent.result.endTime > testProcessSucceededEvent.result.startTime &&
+            testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
+        def rootSucceededEvent = result[7]
+        rootSucceededEvent instanceof TestFinishEvent &&
+            rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
+            rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
+            rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
+            rootSucceededEvent.result instanceof TestSuccessResult &&
+            rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootSucceededEvent.result.endTime > rootSucceededEvent.result.startTime &&
+            rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive test progress events for failed test run"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+            test.ignoreFailures = true
+        """
+
+        file("src/test/java/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     Thread.sleep(100);  // sleep for a moment to ensure test duration is > 0 (due to limited clock resolution)
+                     throw new RuntimeException("broken", new RuntimeException("nope"));
+                }
+            }
+        """
+
+        when:
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 8              // root suite, test process suite, test class suite, test method (each with a start and finish event)
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def rootStartedEvent = result[0]
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
+        def testProcessStartedEvent = result[1]
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        def testClassStartedEvent = result[2]
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == 'Test class example.MyTest' &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        def testStartedEvent = result[3]
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        def testFailedEvent = result[4]
+        testFailedEvent instanceof TestFinishEvent &&
+            testFailedEvent.eventTime >= testFailedEvent.result.endTime &&
+            testFailedEvent.displayName == "Test foo(example.MyTest) failed" &&
+            testFailedEvent.descriptor == testStartedEvent.descriptor &&
+            testFailedEvent.result instanceof TestFailureResult &&
+            testFailedEvent.result.startTime == testStartedEvent.eventTime &&
+            testFailedEvent.result.endTime == testFailedEvent.eventTime &&
+            testFailedEvent.result.failures.size() == 1 &&
+            testFailedEvent.result.failures[0].message == 'broken' &&
+            testFailedEvent.result.failures[0].description.startsWith("java.lang.RuntimeException: broken") &&
+            testFailedEvent.result.failures[0].description.contains("at example.MyTest.foo(MyTest.java:6)") &&
+            testFailedEvent.result.failures[0].causes.size() == 1 &&
+            testFailedEvent.result.failures[0].causes[0].message == 'nope' &&
+            testFailedEvent.result.failures[0].causes[0].causes.empty
+        def testClassFailedEvent = result[5]
+        testClassFailedEvent instanceof TestFinishEvent &&
+            testClassFailedEvent.eventTime >= testClassFailedEvent.result.endTime &&
+            testClassFailedEvent.displayName == "Test class example.MyTest failed" &&
+            testClassFailedEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassFailedEvent.result instanceof TestFailureResult &&
+            testClassFailedEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassFailedEvent.result.endTime == testClassFailedEvent.eventTime &&
+            testClassFailedEvent.result.failures.size() == 0
+        def testProcessFailedEvent = result[6]
+        testProcessFailedEvent instanceof TestFinishEvent &&
+            testProcessFailedEvent.eventTime >= testProcessFailedEvent.result.endTime &&
+            testProcessFailedEvent.displayName == "Gradle Test Executor 2 failed" &&
+            testProcessFailedEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessFailedEvent.result instanceof TestFailureResult &&
+            testProcessFailedEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessFailedEvent.result.endTime == testProcessFailedEvent.eventTime &&
+            testProcessFailedEvent.result.failures.size() == 0
+        def rootFailedEvent = result[7]
+        rootFailedEvent instanceof TestFinishEvent &&
+            rootFailedEvent.eventTime >= rootFailedEvent.result.endTime &&
+            rootFailedEvent.displayName == "Gradle Test Run :test failed" &&
+            rootFailedEvent.descriptor == rootStartedEvent.descriptor &&
+            rootFailedEvent.result instanceof TestFailureResult &&
+            rootFailedEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootFailedEvent.result.endTime == rootFailedEvent.eventTime &&
+            rootFailedEvent.result.failures.size() == 0
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "receive test progress events for skipped test run"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/test/java/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Ignore @org.junit.Test public void foo() throws Exception {
+                     Thread.sleep(100);  // sleep for a moment to ensure test duration is > 0 (due to limited clock resolution)
+                     org.junit.Assert.assertEquals(1, 2);
+                }
+            }
+        """
+
+        when:
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then:
+        result.size() % 2 == 0          // same number of start events as finish events
+        result.size() == 8              // root suite, test process suite, test class suite, test method (each with a start and finish event)
+        result.each {
+            assert it.displayName == it.toString()
+            assert it.descriptor.displayName == it.descriptor.toString()
+        }
+
+        def rootStartedEvent = result[0]
+        rootStartedEvent instanceof TestStartEvent &&
+            rootStartedEvent.eventTime > 0 &&
+            rootStartedEvent.displayName == "Gradle Test Run :test started" &&
+            rootStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            rootStartedEvent.descriptor.name == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.displayName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.suiteName == 'Gradle Test Run :test' &&
+            rootStartedEvent.descriptor.className == null &&
+            rootStartedEvent.descriptor.methodName == null &&
+            rootStartedEvent.descriptor.parent == null
+        def testProcessStartedEvent = result[1]
+        testProcessStartedEvent instanceof TestStartEvent &&
+            testProcessStartedEvent.eventTime > 0 &&
+            testProcessStartedEvent.displayName == "Gradle Test Executor 2 started" &&
+            testProcessStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testProcessStartedEvent.descriptor.name == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.displayName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.suiteName == 'Gradle Test Executor 2' &&
+            testProcessStartedEvent.descriptor.className == null &&
+            testProcessStartedEvent.descriptor.methodName == null &&
+            testProcessStartedEvent.descriptor.parent == rootStartedEvent.descriptor
+        def testClassStartedEvent = result[2]
+        testClassStartedEvent instanceof TestStartEvent &&
+            testClassStartedEvent.eventTime > 0 &&
+            testClassStartedEvent.displayName == "Test class example.MyTest started" &&
+            testClassStartedEvent.descriptor.jvmTestKind == JvmTestKind.SUITE &&
+            testClassStartedEvent.descriptor.name == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.displayName == "Test class example.MyTest" &&
+            testClassStartedEvent.descriptor.suiteName == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.className == 'example.MyTest' &&
+            testClassStartedEvent.descriptor.methodName == null &&
+            testClassStartedEvent.descriptor.parent == testProcessStartedEvent.descriptor
+        def testStartedEvent = result[3]
+        testStartedEvent instanceof TestStartEvent &&
+            testStartedEvent.eventTime > 0 &&
+            testStartedEvent.displayName == "Test foo(example.MyTest) started" &&
+            testStartedEvent.descriptor.jvmTestKind == JvmTestKind.ATOMIC &&
+            testStartedEvent.descriptor.name == 'foo' &&
+            testStartedEvent.descriptor.displayName == 'Test foo(example.MyTest)' &&
+            testStartedEvent.descriptor.suiteName == null &&
+            testStartedEvent.descriptor.className == 'example.MyTest' &&
+            testStartedEvent.descriptor.methodName == 'foo' &&
+            testStartedEvent.descriptor.parent == testClassStartedEvent.descriptor
+        def testSkippedEvent = result[4]
+        testSkippedEvent instanceof TestFinishEvent &&
+            testSkippedEvent.eventTime > 0 &&
+            testSkippedEvent.displayName == "Test foo(example.MyTest) skipped" &&
+            testSkippedEvent.descriptor == testStartedEvent.descriptor &&
+            testSkippedEvent.result instanceof TestSkippedResult &&
+            testSkippedEvent.result.startTime == testStartedEvent.eventTime &&
+            testSkippedEvent.result.endTime == testSkippedEvent.eventTime
+        def testClassSucceededEvent = result[5]
+        testClassSucceededEvent instanceof TestFinishEvent &&
+            testClassSucceededEvent.eventTime >= testClassSucceededEvent.result.endTime &&
+            testClassSucceededEvent.displayName == "Test class example.MyTest succeeded" &&
+            testClassSucceededEvent.descriptor == testClassStartedEvent.descriptor &&
+            testClassSucceededEvent.result instanceof TestSuccessResult &&
+            testClassSucceededEvent.result.startTime == testClassStartedEvent.eventTime &&
+            testClassSucceededEvent.result.endTime == testClassSucceededEvent.eventTime
+        def testProcessSucceededEvent = result[6]
+        testProcessSucceededEvent instanceof TestFinishEvent &&
+            testProcessSucceededEvent.eventTime >= testProcessSucceededEvent.result.endTime &&
+            testProcessSucceededEvent.displayName == "Gradle Test Executor 2 succeeded" &&
+            testProcessSucceededEvent.descriptor == testProcessStartedEvent.descriptor &&
+            testProcessSucceededEvent.result instanceof TestSuccessResult &&
+            testProcessSucceededEvent.result.startTime == testProcessStartedEvent.eventTime &&
+            testProcessSucceededEvent.result.endTime == testProcessSucceededEvent.eventTime
+        def rootSucceededEvent = result[7]
+        rootSucceededEvent instanceof TestFinishEvent &&
+            rootSucceededEvent.eventTime >= rootSucceededEvent.result.endTime &&
+            rootSucceededEvent.displayName == "Gradle Test Run :test succeeded" &&
+            rootSucceededEvent.descriptor == rootStartedEvent.descriptor &&
+            rootSucceededEvent.result instanceof TestSuccessResult &&
+            rootSucceededEvent.result.startTime == rootStartedEvent.eventTime &&
+            rootSucceededEvent.result.endTime == rootSucceededEvent.eventTime
+    }
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "test progress event ids are unique across multiple test workers"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+            test.maxParallelForks = 2
+        """
+
+        file("src/test/java/example/MyTest1.java") << """
+            package example;
+            public class MyTest1 {
+                @org.junit.Test public void alpha() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void beta() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void gamma() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void delta() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+        file("src/test/java/example/MyTest2.java") << """
+            package example;
+            public class MyTest2 {
+                @org.junit.Test public void one() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void two() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void three() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void four() throws Exception {
+                     Thread.sleep(100);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+
+        when:
+        Queue<TestProgressEvent> result = new ConcurrentLinkedQueue<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then: "start and end event is sent for each node in the test tree"
+        result.size() % 2 == 0                // same number of start events as finish events
+        result.size() == 2 * (1 + 2 + 2 + 8)  // 1 root suite, 2 test processes, 2 tests classes, 8 tests (each with a start and finish event)
+
+        then: "each node in the test tree has its own description"
+        result.collect { it.descriptor }.toSet().size() == 13
+
+        then: "number of nodes under the root suite is equal to the number of test worker processes"
+        result.findAll { it.descriptor.parent == null }.toSet().size() == 2  // 1 root suite with no further parent (start & finish events)
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.4")
+    def "test progress event ids are unique across multiple test tasks, even when run in parallel"() {
+        given:
+        projectDir.createFile('settings.gradle') << """
+            include ':sub1'
+            include ':sub2'
+        """
+        projectDir.createFile('build.gradle')
+
+        [projectDir.createDir('sub1'), projectDir.createDir('sub2')].eachWithIndex { TestFile it, def index ->
+            it.file('build.gradle') << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true
+            test.maxParallelForks = 2
+            test.ignoreFailures = true
+        """
+            it.file("src/test/java/sub/MyUnitTest1${index}.java") << """
+            package sub;
+            public class MyUnitTest1$index {
+                @org.junit.Test public void alpha() throws Exception {
+                     Thread.sleep(300);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void beta() throws Exception {
+                     Thread.sleep(1000);
+                     org.junit.Assert.assertEquals(2, 1);
+                }
+            }
+        """
+            it.file("src/test/java/sub/MyUnitTest2${index}.java") << """
+            package sub;
+            public class MyUnitTest2$index {
+                @org.junit.Test public void one() throws Exception {
+                     Thread.sleep(1000);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void two() throws Exception {
+                     Thread.sleep(300);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void three() throws Exception {
+                     Thread.sleep(300);
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+                @org.junit.Test public void four() throws Exception {
+                     Thread.sleep(300);
+                     org.junit.Assert.assertEquals(3, 1);
+                }
+            }
+        """
+        }
+
+        when:
+        Queue<TestProgressEvent> result = new ConcurrentLinkedQueue<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).withArguments('--parallel').run()
+        }
+
+        then: "start and end event is sent for each node in the test tree"
+        result.size() % 2 == 0                    // same number of start events as finish events
+        result.size() == 2 * 2 * (1 + 2 + 2 + 6)  // two test tasks with each: 1 root suite, 2 test processes, 2 tests classes, 6 tests (each with a start and finish event)
+
+        then: "each node in the test tree has its own description"
+        result.collect { it.descriptor }.toSet().size() == 2 * 11
+
+        then: "number of nodes under the root suite is equal to the number of test worker processes"
+        result.findAll { it.descriptor.parent == null }.toSet().size() == 4  // 2 root suites with no further parent (start & finish events)
+
+        then: "names for root suites and worker suites are consistent"
+        result.findAll { it.descriptor.name =~ 'Gradle Test Run :sub[1|2]:test' }.toSet().size() == 4  // 2 root suites for 2 tasks (start & finish events)
+        result.findAll { it.descriptor.name =~ 'Gradle Test Executor \\d+' }.toSet().size() == 8       // 2 test processes for each task (start & finish events)
+    }
+
+    @TargetGradleVersion(">=2.5")
+    @ToolingApiVersion(">=2.4")
+    @NotYetImplemented
+    def "should receive test events from buildSrc"() {
+        buildFile << """task dummy()"""
+        file("buildSrc/build.gradle") << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+        file("buildSrc/src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+
+        when:
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection { ProjectConnection connection ->
+            connection.newBuild().forTasks('dummy').addProgressListener({ ProgressEvent event ->
+                result << (event as TestProgressEvent)
+            }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then:
+        !result.empty
+    }
+
+    @TargetGradleVersion(">=2.5")
+    @ToolingApiVersion(">=2.5")
+    def "top-level test operation has test task as parent iff task listener is attached"() {
+        given:
+        goodCode()
+
+        when: 'listening to test progress events and task listener is attached'
+        List<TestProgressEvent> result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        if (event instanceof TestProgressEvent) {
+                            result << event
+                        }
+                    }
+                }, EnumSet.of(OperationType.TASK, OperationType.TEST)).run()
+        }
+
+        then: 'the parent of the root test progress event is the test task that triggered the tests'
+        !result.isEmpty()
+        [result.first(), result.last()].each { def event ->
+            assert event.descriptor.parent instanceof TaskOperationDescriptor
+            assert event.descriptor.parent.name == ':test'
+        }
+
+        when: 'listening to test progress events and no task listener is attached'
+        result = new ArrayList<TestProgressEvent>()
+        withConnection {
+            ProjectConnection connection ->
+                connection.newBuild().withArguments('--rerun-tasks').forTasks('test').addProgressListener(new ProgressListener() {
+                    @Override
+                    void statusChanged(ProgressEvent event) {
+                        result << (event as TestProgressEvent)
+                    }
+                }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then: 'the parent of the root test progress event is null'
+        !result.isEmpty()
+        [result.first(), result.last()].each { def event ->
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+            compileTestJava.options.fork = true  // forked as 'Gradle Test Executor 1'
+        """
+
+        file("src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy
new file mode 100644
index 0000000..48d8317
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+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.ProjectConnection
+import org.gradle.tooling.events.ProgressEvent
+import org.gradle.tooling.events.OperationType
+
+class TestProgressDaemonErrorsCrossVersionSpec extends ToolingApiSpecification {
+
+    void setup() {
+        toolingApi.requireIsolatedDaemons()
+    }
+
+    @TargetGradleVersion(">=2.4")
+    @ToolingApiVersion(">=2.5")
+    def "should received failed event when daemon disappears unexpectedly with TAPI higher 2.4"() {
+        given:
+        goodCode()
+
+        when:
+        def result = []
+        withConnection { ProjectConnection connection ->
+            connection.newBuild().forTasks('test').addProgressListener({ ProgressEvent event ->
+                result << event
+                toolingApi.daemons.daemon.kill()
+            }, EnumSet.of(OperationType.TEST)).run()
+        }
+
+        then: "build fails with a DaemonDisappearedException"
+        GradleConnectionException ex = thrown()
+        ex.cause.message.contains('Gradle build daemon disappeared unexpectedly')
+
+        and:
+        !result.empty
+    }
+
+    def goodCode() {
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.12' }
+        """
+
+        file("src/test/java/example/MyTest.java") << """
+            package example;
+            public class MyTest {
+                @org.junit.Test public void foo() throws Exception {
+                     org.junit.Assert.assertEquals(1, 1);
+                }
+            }
+        """
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ToolingApiEclipseModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..041ab5e
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r25/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.r25
+
+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.eclipse.EclipseProject
+
+class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
+
+    @ToolingApiVersion(">=2.5")
+    @TargetGradleVersion(">=2.5")
+    def "export classpath entry option is reflected in eclipse model"() {
+
+        projectDir.file('settings.gradle').text = '''
+include 'a'
+rootProject.name = 'root'
+'''
+
+        projectDir.file('build.gradle').text = '''
+
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    jcenter()
+}
+
+configurations {
+    provided
+}
+
+dependencies {
+    compile project(':a')
+    compile 'com.google.guava:guava:17.0'
+    provided 'org.slf4j:slf4j-log4j12:1.7.12'
+}
+
+eclipse {
+    classpath {
+        plusConfigurations += [ configurations.provided ]
+    }
+}
+
+configure(project(':a')){
+    apply plugin:'java'
+}'''
+
+        when:
+        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        rootProject.projectDependencies.find {it.targetProject.name == "a"}.exported ==false
+        rootProject.classpath.find { it.file.name.contains("guava") }.exported == false
+        rootProject.classpath.find { it.file.name.contains("slf4j-log4j") }.exported == false
+    }
+
+    @TargetGradleVersion(">=2.5")
+    def "transitive dependencies are listed as direct dependencies in the eclipse model"() {
+        def mavenRepo = new MavenFileRepository(file("maven-repo"));
+        mavenRepo.module('someGroup', 'someArtifact', '17.0').publish()
+        mavenRepo.module('someGroup', 'someArtifact', '16.0.1').publish()
+
+        projectDir.file('settings.gradle').text = '''
+include 'a', 'b', 'c'
+rootProject.name = 'root'
+'''
+
+        projectDir.file('build.gradle').text = """
+
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+}
+
+
+configure(project(':a')) {
+    dependencies {
+        compile 'someGroup:someArtifact:17.0'
+        compile project(':b')
+    }
+}
+
+
+configure(project(':b')) {
+    dependencies {
+        compile project(':c')
+    }
+}
+
+configure(project(':c')) {
+    dependencies {
+        compile 'someGroup:someArtifact:16.0.1'
+    }
+}
+"""
+
+        when:
+        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+        EclipseProject aProject = rootProject.children.find { it.name == 'a'}
+        EclipseProject bProject = rootProject.children.find { it.name == 'b'}
+        EclipseProject cProject = rootProject.children.find { it.name == 'c'}
+        then:
+        aProject.classpath.find { it.file.name == "someArtifact-17.0.jar" }
+        bProject.classpath.find { it.file.name == "someArtifact-16.0.1.jar" }
+        cProject.classpath.find { it.file.name == "someArtifact-16.0.1.jar" }
+    }
+}
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
index d5046b2..a6fac9a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionExecuter.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildActionExecuter.java
@@ -17,11 +17,12 @@
 package org.gradle.tooling;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.OperationType;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 /**
  * Used to execute a {@link BuildAction} in the build process.
@@ -34,65 +35,94 @@ public interface BuildActionExecuter<T> extends LongRunningOperation {
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> withArguments(String... arguments);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> setStandardOutput(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> setStandardError(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
     @Incubating
+    @Override
     BuildActionExecuter<T> setColorOutput(boolean colorOutput);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> setStandardInput(InputStream inputStream);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> setJavaHome(File javaHome);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> setJvmArguments(String... jvmArguments);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
+    @Override
     BuildActionExecuter<T> addProgressListener(ProgressListener listener);
 
     /**
      * {@inheritDoc}
-     * @since 2.4
+     *
+     * @since 2.5
+     */
+    @Incubating
+    @Override
+    BuildActionExecuter<T> addProgressListener(org.gradle.tooling.events.ProgressListener listener);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.5
      */
     @Incubating
-    BuildActionExecuter<T> addTestProgressListener(TestProgressListener listener);
+    @Override
+    BuildActionExecuter<T> addProgressListener(org.gradle.tooling.events.ProgressListener listener, Set<OperationType> eventTypes);
 
     /**
      * {@inheritDoc}
+     *
      * @since 2.3
      */
     @Incubating
+    @Override
     BuildActionExecuter<T> withCancellationToken(CancellationToken cancellationToken);
 
     /**
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 bc21d78..eeabd39 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
@@ -16,13 +16,14 @@
 package org.gradle.tooling;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.OperationType;
 import org.gradle.tooling.model.Launchable;
 import org.gradle.tooling.model.Task;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 /**
  * A {@code BuildLauncher} allows you to configure and execute a Gradle build.
@@ -78,18 +79,21 @@ public interface BuildLauncher extends LongRunningOperation {
      * {@inheritDoc}
      * @since 1.0
      */
+    @Override
     BuildLauncher withArguments(String ... arguments);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     BuildLauncher setStandardOutput(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     BuildLauncher setStandardError(OutputStream outputStream);
 
     /**
@@ -97,44 +101,61 @@ public interface BuildLauncher extends LongRunningOperation {
      * @since 2.3
      */
     @Incubating
+    @Override
     BuildLauncher setColorOutput(boolean colorOutput);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-7
      */
+    @Override
     BuildLauncher setStandardInput(InputStream inputStream);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-8
      */
+    @Override
     BuildLauncher setJavaHome(File javaHome);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-9
      */
+    @Override
     BuildLauncher setJvmArguments(String... jvmArguments);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     BuildLauncher addProgressListener(ProgressListener listener);
 
     /**
      * {@inheritDoc}
-     * @since 2.4
+     *
+     * @since 2.5
      */
     @Incubating
-    BuildLauncher addTestProgressListener(TestProgressListener listener);
+    @Override
+    BuildLauncher addProgressListener(org.gradle.tooling.events.ProgressListener listener);
 
     /**
      * {@inheritDoc}
+     * @since 2.5
+     */
+    @Incubating
+    @Override
+    BuildLauncher addProgressListener(org.gradle.tooling.events.ProgressListener listener, Set<OperationType> eventTypes);
+
+    /**
+     * {@inheritDoc}
+     *
      * @since 2.3
      */
     @Incubating
+    @Override
     BuildLauncher withCancellationToken(CancellationToken cancellationToken);
 
     /**
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ListenerFailedException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ListenerFailedException.java
new file mode 100644
index 0000000..0047971
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ListenerFailedException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.List;
+
+/**
+ * Thrown whenever a listener fails with an exception, which in general implies that
+ * the build completed like it should, but that one of the listeners failed with an
+ * exception.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public class ListenerFailedException extends GradleConnectionException {
+    private final List<? extends Throwable> listenerFailures;
+
+    public ListenerFailedException(String message, List<? extends Throwable> failures) {
+        super(message);
+        listenerFailures = failures;
+        if (!failures.isEmpty()) {
+            initCause(failures.get(0));
+        }
+    }
+
+    public List<? extends Throwable> getCauses() {
+        return listenerFailures;
+    }
+}
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 4bfb312..94f90e3 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
@@ -16,11 +16,12 @@
 package org.gradle.tooling;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.OperationType;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 /**
  * Offers ways to communicate both ways with a Gradle operation, be it building a model or running tasks.
@@ -95,8 +96,8 @@ public interface LongRunningOperation {
      *
      * @param javaHome to use for the Gradle process
      * @return this
-     * @since 1.0-milestone-8
      * @throws IllegalArgumentException when supplied javaHome is not a valid folder.
+     * @since 1.0-milestone-8
      */
     LongRunningOperation setJavaHome(File javaHome) throws IllegalArgumentException;
 
@@ -148,7 +149,7 @@ public interface LongRunningOperation {
      * @return this
      * @since 1.0
      */
-    LongRunningOperation withArguments(String ... arguments);
+    LongRunningOperation withArguments(String... arguments);
 
     /**
      * Adds a progress listener which will receive progress events as the operation runs.
@@ -160,14 +161,25 @@ public interface LongRunningOperation {
     LongRunningOperation addProgressListener(ProgressListener listener);
 
     /**
-     * Adds a test progress listener which will receive test progress events as the operation runs.
+     * Adds a progress listener which will receive progress events of all types as the operation runs.
+     *
+     * @param listener The listener
+     * @return this
+     * @since 2.5
+     */
+    @Incubating
+    LongRunningOperation addProgressListener(org.gradle.tooling.events.ProgressListener listener);
+
+    /**
+     * Adds a progress listener which will receive progress events as the operations of the requested type run.
      *
      * @param listener The listener
+     * @param operationTypes The types of operations to receive progress events for.
      * @return this
-     * @since 2.4
+     * @since 2.5
      */
     @Incubating
-    LongRunningOperation addTestProgressListener(TestProgressListener listener);
+    LongRunningOperation addProgressListener(org.gradle.tooling.events.ProgressListener listener, Set<OperationType> operationTypes);
 
     /**
      * Sets the cancellation token to use to cancel the operation if required.
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 cb2bd68..1d4136d 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,11 +16,12 @@
 package org.gradle.tooling;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.OperationType;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 /**
  * A {@code ModelBuilder} allows you to fetch a snapshot of some model for a project or a build.
@@ -73,18 +74,21 @@ public interface ModelBuilder<T> extends LongRunningOperation {
      * {@inheritDoc}
      * @since 1.0
      */
+    @Override
     ModelBuilder<T> withArguments(String ... arguments);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     ModelBuilder<T> setStandardOutput(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     ModelBuilder<T> setStandardError(OutputStream outputStream);
 
     /**
@@ -92,44 +96,60 @@ public interface ModelBuilder<T> extends LongRunningOperation {
      * @since 2.3
      */
     @Incubating
+    @Override
     ModelBuilder<T> setColorOutput(boolean colorOutput);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-7
      */
+    @Override
     ModelBuilder<T> setStandardInput(InputStream inputStream);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-8
      */
+    @Override
     ModelBuilder<T> setJavaHome(File javaHome);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-9
      */
+    @Override
     ModelBuilder<T> setJvmArguments(String... jvmArguments);
 
     /**
      * {@inheritDoc}
      * @since 1.0-milestone-3
      */
+    @Override
     ModelBuilder<T> addProgressListener(ProgressListener listener);
 
     /**
      * {@inheritDoc}
-     * @since 2.4
+     *
+     * @since 2.5
+     */
+    @Incubating
+    @Override
+    ModelBuilder<T> addProgressListener(org.gradle.tooling.events.ProgressListener listener);
+
+    /**
+     * {@inheritDoc}
+     * @since 2.5
      */
     @Incubating
-    ModelBuilder<T> addTestProgressListener(TestProgressListener listener);
+    @Override
+    ModelBuilder<T> addProgressListener(org.gradle.tooling.events.ProgressListener listener, Set<OperationType> eventTypes);
 
     /**
      * {@inheritDoc}
      * @since 2.3
      */
     @Incubating
+    @Override
     ModelBuilder<T> withCancellationToken(CancellationToken cancellationToken);
 
     /**
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationDescriptor.java
index f940090..27f8879 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationDescriptor.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationDescriptor.java
@@ -23,20 +23,23 @@ import org.gradle.api.Nullable;
  *
  * <p>You can use {@code equals()} to determine whether 2 different descriptors refer to the same operation.</p>
  *
+ * <p>The subtypes of this interface define specific types of operations, such as task execution.</p>
+ *
  * @since 2.4
  */
 @Incubating
 public interface OperationDescriptor {
 
     /**
-     * Returns the name of the operation. This name does not necessarily uniquely identify the operation.
+     * Returns the name of the operation. This name does not necessarily uniquely identify the operation. However, the name can be used
+     * by a human to disambiguate between the children of a given operation.
      *
      * @return The name of the operation.
      */
     String getName();
 
     /**
-     * Returns a human consumable display name for the operation.
+     * Returns a human consumable display name for the operation. This display name provides enough context for a human to uniquely identify the operation.
      *
      * @return The display name of the operation.
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationType.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationType.java
new file mode 100644
index 0000000..6420e18
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/OperationType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events;
+
+/**
+ * Enumerates the different types of operations for which progress events can be received.
+ *
+ * @see org.gradle.tooling.LongRunningOperation#addProgressListener(ProgressListener, java.util.Set)
+ */
+public enum OperationType {
+
+    /**
+     * Flag for test operation progress events.
+     */
+    TEST,
+
+    /**
+     * Flag for task operation progress events.
+     */
+    TASK,
+
+    /**
+     * Flag for operations with no specific type.
+     */
+    GENERIC
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/ProgressListener.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/ProgressListener.java
new file mode 100644
index 0000000..c1e1c2d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/ProgressListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A listener which is notified when operations that are executed as part of running a build make progress.
+ *
+ * @see org.gradle.tooling.LongRunningOperation#addProgressListener(ProgressListener)
+ * @see org.gradle.tooling.LongRunningOperation#addProgressListener(ProgressListener, java.util.EnumSet)
+ * @since 2.5
+ */
+ at Incubating
+public interface ProgressListener {
+
+    /**
+     * Called when the execution of an operation progresses.
+     *
+     * The following operation-specific events are currently issued:
+     * <ul>
+     *     <li>{@link org.gradle.tooling.events.test.TestStartEvent}</li>
+     *     <li>{@link org.gradle.tooling.events.test.TestFinishEvent}</li>
+     *     <li>{@link org.gradle.tooling.events.task.TaskStartEvent}</li>
+     *     <li>{@link org.gradle.tooling.events.task.TaskFinishEvent}</li>
+     * </ul>
+     *
+     * For all other operations, the following generic events are currently issued :
+     * <ul>
+     *     <li>{@link StartEvent}</li>
+     *     <li>{@link FinishEvent}</li>
+     * </ul>
+     *
+     * You can find out more about the operation for which progress is reported
+     * by querying the descriptor using {@link org.gradle.tooling.events.ProgressEvent#getDescriptor()}.
+     *
+     * @param event An event describing the operation progress.
+     * @see org.gradle.tooling.events.test.TestProgressEvent
+     * @see org.gradle.tooling.events.task.TaskProgressEvent
+     * @see org.gradle.tooling.events.ProgressEvent
+     */
+    void statusChanged(ProgressEvent event);
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseFinishEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseFinishEvent.java
deleted file mode 100644
index e186e90..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseFinishEvent.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.events.internal;
-
-import org.gradle.tooling.events.FinishEvent;
-import org.gradle.tooling.events.OperationDescriptor;
-import org.gradle.tooling.events.OperationResult;
-
-/**
- * Base implementation of the {@code FinishEvent} interface.
- */
-public abstract class BaseFinishEvent extends BaseProgressEvent implements FinishEvent {
-
-    private final OperationResult result;
-
-    public BaseFinishEvent(long eventTime, String displayName, OperationDescriptor descriptor, OperationResult result) {
-        super(eventTime, displayName, descriptor);
-        this.result = result;
-    }
-
-    @Override
-    public OperationResult getResult() {
-        return result;
-    }
-
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseStartEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseStartEvent.java
deleted file mode 100644
index 2ea0837..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/BaseStartEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.tooling.events.internal;
-
-import org.gradle.tooling.events.OperationDescriptor;
-import org.gradle.tooling.events.StartEvent;
-
-/**
- * Base implementation of the {@code StartEvent} interface.
- */
-public abstract class BaseStartEvent extends BaseProgressEvent implements StartEvent {
-
-    protected BaseStartEvent(long eventTime, String displayName, OperationDescriptor descriptor) {
-        super(eventTime, displayName, descriptor);
-    }
-
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultFinishEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultFinishEvent.java
new file mode 100644
index 0000000..a57cccc
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultFinishEvent.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.internal;
+
+import org.gradle.tooling.events.FinishEvent;
+import org.gradle.tooling.events.OperationDescriptor;
+import org.gradle.tooling.events.OperationResult;
+
+/**
+ * Base implementation of the {@code FinishEvent} interface.
+ */
+public class DefaultFinishEvent extends BaseProgressEvent implements FinishEvent {
+
+    private final OperationResult result;
+
+    public DefaultFinishEvent(long eventTime, String displayName, OperationDescriptor descriptor, OperationResult result) {
+        super(eventTime, displayName, descriptor);
+        this.result = result;
+    }
+
+    @Override
+    public OperationResult getResult() {
+        return result;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationDescriptor.java
new file mode 100644
index 0000000..066c844
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationDescriptor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.tooling.events.OperationDescriptor;
+
+/**
+ * Implementation of the {@code BuildOperationDescriptor} interface.
+ */
+public class DefaultOperationDescriptor implements OperationDescriptor {
+
+    private final String name;
+    private final String displayName;
+    private final OperationDescriptor parent;
+
+    public DefaultOperationDescriptor(String name, String displayName, OperationDescriptor parent) {
+        this.name = name;
+        this.displayName = displayName;
+        this.parent = parent;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Nullable
+    @Override
+    public OperationDescriptor getParent() {
+        return parent;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationFailureResult.java
new file mode 100644
index 0000000..3f8b6b8
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationFailureResult.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.internal;
+
+import org.gradle.tooling.Failure;
+import org.gradle.tooling.events.FailureResult;
+
+import java.util.List;
+
+/**
+ * Implementation of the {@code BuildFailureResult} interface.
+ */
+public class DefaultOperationFailureResult implements FailureResult {
+
+    private final long startTime;
+    private final long endTime;
+    private final List<? extends Failure> failures;
+
+    public DefaultOperationFailureResult(long startTime, long endTime, List<? extends Failure> failures) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.failures = failures;
+    }
+
+    @Override
+    public long getStartTime() {
+        return startTime;
+    }
+
+    @Override
+    public long getEndTime() {
+        return endTime;
+    }
+
+    @Override
+    public List<? extends Failure> getFailures() {
+        return failures;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationSuccessResult.java
new file mode 100644
index 0000000..c374f43
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultOperationSuccessResult.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.internal;
+
+import org.gradle.tooling.events.SuccessResult;
+
+/**
+ * Implementation of the {@code BuildSuccessResult} interface.
+ */
+public class DefaultOperationSuccessResult implements SuccessResult {
+
+    private final long startTime;
+    private final long endTime;
+
+    public DefaultOperationSuccessResult(long startTime, long endTime) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+    }
+
+    @Override
+    public long getStartTime() {
+        return startTime;
+    }
+
+    @Override
+    public long getEndTime() {
+        return endTime;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultStartEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultStartEvent.java
new file mode 100644
index 0000000..e239d07
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/internal/DefaultStartEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.internal;
+
+import org.gradle.tooling.events.OperationDescriptor;
+import org.gradle.tooling.events.StartEvent;
+
+/**
+ * Base implementation of the {@code StartEvent} interface.
+ */
+public class DefaultStartEvent extends BaseProgressEvent implements StartEvent {
+
+    public DefaultStartEvent(long eventTime, String displayName, OperationDescriptor descriptor) {
+        super(eventTime, displayName, descriptor);
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFailureResult.java
new file mode 100644
index 0000000..7e46cbb
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFailureResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.FailureResult;
+
+/**
+ * Describes how a task operation finished with failures.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskFailureResult extends TaskOperationResult, FailureResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFinishEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFinishEvent.java
new file mode 100644
index 0000000..b218248
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskFinishEvent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.FinishEvent;
+
+/**
+ * An event that informs about a task having finished its execution. You can query the result of the task using {@link #getResult()}.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskFinishEvent extends TaskProgressEvent, FinishEvent {
+
+    /**
+     * Returns the result of the finished task operation. Currently, the result will be one of the following sub-types:
+     *
+     * <ul>
+     *     <li>{@link TaskSuccessResult}</li>
+     *     <li>{@link TaskSkippedResult}</li>
+     *     <li>{@link TaskFailureResult}</li>
+     * </ul>
+     *
+     * @return the result of the finished task operation
+     */
+    @Override
+    TaskOperationResult getResult();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationDescriptor.java
new file mode 100644
index 0000000..3871ea9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationDescriptor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.OperationDescriptor;
+
+/**
+ * Describes a task operation for which an event has occurred.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskOperationDescriptor extends OperationDescriptor {
+    /**
+     * Returns the path of the task.
+     */
+    String getTaskPath();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationResult.java
new file mode 100644
index 0000000..456e9b5
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskOperationResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.OperationResult;
+
+/**
+ * Describes the result of running a task operation.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskOperationResult extends OperationResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskProgressEvent.java
new file mode 100644
index 0000000..4d7f517
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskProgressEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.ProgressEvent;
+
+/**
+ * Root interface for all events that signal progress while executing a task.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskProgressEvent extends ProgressEvent {
+    /**
+     * Returns the description of the task for which progress is reported.
+     *
+     * @return The description of the underlying task operation.
+     */
+    @Override
+    TaskOperationDescriptor getDescriptor();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSkippedResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSkippedResult.java
new file mode 100644
index 0000000..e2ee5a8
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSkippedResult.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.SkippedResult;
+
+/**
+ * Describes how a task operation was skipped.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskSkippedResult extends TaskOperationResult, SkippedResult {
+
+    /**
+     * Returns a message describing the reason for skipping the task.
+     *
+     * @return the message describing the skip reason
+     */
+    String getSkipMessage();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskStartEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskStartEvent.java
new file mode 100644
index 0000000..9f0937d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskStartEvent.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.StartEvent;
+
+/**
+ * An event that informs about a task having started its execution.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskStartEvent extends TaskProgressEvent, StartEvent {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSuccessResult.java
new file mode 100644
index 0000000..dd86694
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/TaskSuccessResult.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task;
+
+import org.gradle.api.Incubating;
+import org.gradle.tooling.events.SuccessResult;
+
+/**
+ * Describes how a task operation finished successfully.
+ *
+ * @since 2.5
+ */
+ at Incubating
+public interface TaskSuccessResult extends TaskOperationResult, SuccessResult {
+    /**
+     * Returns whether this task was uptodate.
+     *
+     * @return {@code true} if this task was uptodate
+     */
+    boolean isUpToDate();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFailureResult.java
new file mode 100644
index 0000000..0cf5891
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFailureResult.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.Failure;
+import org.gradle.tooling.events.internal.DefaultOperationFailureResult;
+import org.gradle.tooling.events.task.TaskFailureResult;
+
+import java.util.List;
+
+/**
+ * Implementation of the {@code TaskFailureResult} interface.
+ */
+public final class DefaultTaskFailureResult extends DefaultOperationFailureResult implements TaskFailureResult {
+
+    public DefaultTaskFailureResult(long startTime, long endTime, List<? extends Failure> failures) {
+        super(startTime, endTime, failures);
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFinishEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFinishEvent.java
new file mode 100644
index 0000000..d18eb50
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskFinishEvent.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.events.internal.DefaultFinishEvent;
+import org.gradle.tooling.events.task.TaskFinishEvent;
+import org.gradle.tooling.events.task.TaskOperationDescriptor;
+import org.gradle.tooling.events.task.TaskOperationResult;
+
+/**
+ * Implementation of the {@code TaskFinishEvent} interface.
+ */
+public final class DefaultTaskFinishEvent extends DefaultFinishEvent implements TaskFinishEvent {
+
+    public DefaultTaskFinishEvent(long eventTime, String displayName, TaskOperationDescriptor descriptor, TaskOperationResult result) {
+        super(eventTime, displayName, descriptor, result);
+    }
+
+    @Override
+    public TaskOperationDescriptor getDescriptor() {
+        return (TaskOperationDescriptor) super.getDescriptor();
+    }
+
+    @Override
+    public TaskOperationResult getResult() {
+        return (TaskOperationResult) super.getResult();
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskOperationDescriptor.java
new file mode 100644
index 0000000..d58a10f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskOperationDescriptor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.events.OperationDescriptor;
+import org.gradle.tooling.events.internal.DefaultOperationDescriptor;
+import org.gradle.tooling.events.task.TaskOperationDescriptor;
+
+/**
+ * Implementation of the {@code TaskOperationDescriptor} interface.
+ */
+public final class DefaultTaskOperationDescriptor extends DefaultOperationDescriptor implements TaskOperationDescriptor {
+
+    private final String taskPath;
+
+    public DefaultTaskOperationDescriptor(String name, String displayName, String taskPath, OperationDescriptor parent) {
+        super(name, displayName, parent);
+        this.taskPath = taskPath;
+    }
+
+    @Override
+    public String getTaskPath() {
+        return taskPath;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSkippedResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSkippedResult.java
new file mode 100644
index 0000000..75ce7b9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSkippedResult.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.events.task.TaskSkippedResult;
+
+/**
+ * Implementation of the {@code TaskSkippedResult} interface.
+ */
+public final class DefaultTaskSkippedResult implements TaskSkippedResult {
+
+    private final long startTime;
+    private final long endTime;
+    private final String skipMessage;
+
+    public DefaultTaskSkippedResult(long startTime, long endTime, String skipMessage) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.skipMessage = skipMessage;
+    }
+
+    @Override
+    public long getStartTime() {
+        return startTime;
+    }
+
+    @Override
+    public long getEndTime() {
+        return endTime;
+    }
+
+    @Override
+    public String getSkipMessage() {
+        return this.skipMessage;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskStartEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskStartEvent.java
new file mode 100644
index 0000000..bb74c8c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskStartEvent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.events.internal.DefaultStartEvent;
+import org.gradle.tooling.events.task.TaskOperationDescriptor;
+import org.gradle.tooling.events.task.TaskStartEvent;
+
+/**
+ * Implementation of the {@code TaskStartEvent} interface.
+ */
+public final class DefaultTaskStartEvent extends DefaultStartEvent implements TaskStartEvent {
+
+    public DefaultTaskStartEvent(long eventTime, String displayName, TaskOperationDescriptor descriptor) {
+        super(eventTime, displayName, descriptor);
+    }
+
+    @Override
+    public TaskOperationDescriptor getDescriptor() {
+        return (TaskOperationDescriptor) super.getDescriptor();
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSuccessResult.java
new file mode 100644
index 0000000..4703e33
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/internal/DefaultTaskSuccessResult.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.task.internal;
+
+import org.gradle.tooling.events.internal.DefaultOperationSuccessResult;
+import org.gradle.tooling.events.task.TaskSuccessResult;
+
+/**
+ * Implementation of the {@code TaskSuccessResult} interface.
+ */
+public final class DefaultTaskSuccessResult extends DefaultOperationSuccessResult implements TaskSuccessResult {
+
+    private final boolean upToDate;
+
+    public DefaultTaskSuccessResult(long startTime, long endTime, boolean upToDate) {
+        super(startTime, endTime);
+        this.upToDate = upToDate;
+    }
+
+    @Override
+    public boolean isUpToDate() {
+        return this.upToDate;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/package-info.java
new file mode 100644
index 0000000..504433a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/task/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Task execution specific interfaces and classes related to event notifications.
+ */
+package org.gradle.tooling.events.task;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestFailureResult.java
index ae60f30..71af543 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestFailureResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestFailureResult.java
@@ -17,11 +17,8 @@
 package org.gradle.tooling.events.test;
 
 import org.gradle.api.Incubating;
-import org.gradle.tooling.Failure;
 import org.gradle.tooling.events.FailureResult;
 
-import java.util.List;
-
 /**
  * Describes how a test operation finished with failures.
  *
@@ -29,13 +26,4 @@ import java.util.List;
  */
 @Incubating
 public interface TestFailureResult extends TestOperationResult, FailureResult {
-
-    /**
-     * Returns the exceptions that occurred while running the test operation, if any.
-     *
-     * @return the exceptions, empty if the test operation failed without any exceptions
-     */
-    @Override
-    List<? extends Failure> getFailures();
-
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestProgressListener.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestProgressListener.java
deleted file mode 100644
index 9d4d97a..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/TestProgressListener.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tooling.events.test;
-
-import org.gradle.api.Incubating;
-
-/**
- * A listener which is notified when the tests that are executed as part of running a build make progress.
- *
- * @see org.gradle.tooling.LongRunningOperation#addTestProgressListener(TestProgressListener)
- * @since 2.4
- */
- at Incubating
-public interface TestProgressListener {
-
-    /**
-     * Called when the test execution progresses.
-     *
-     * The following events are currently issued:
-     * <ul>
-     *    <li>{@link TestStartEvent}</li>
-     *    <li>{@link TestFinishEvent}</li>
-     * </ul>
-     *
-     * You can find out more about the test operation for which progress is reported
-     * by querying the test descriptor using {@link TestProgressEvent#getDescriptor()}.
-     *
-     * @param event An event describing the test operation progress.
-     * @see TestProgressEvent#getDescriptor()
-     */
-    void statusChanged(TestProgressEvent event);
-
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultJvmTestOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultJvmTestOperationDescriptor.java
new file mode 100644
index 0000000..6e920a3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultJvmTestOperationDescriptor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.test.internal;
+
+import org.gradle.api.Nullable;
+import org.gradle.tooling.events.OperationDescriptor;
+import org.gradle.tooling.events.test.JvmTestKind;
+import org.gradle.tooling.events.test.JvmTestOperationDescriptor;
+
+/**
+ * Implementation of the {@code JvmTestOperationDescriptor} interface.
+ */
+public final class DefaultJvmTestOperationDescriptor extends DefaultTestOperationDescriptor implements JvmTestOperationDescriptor {
+
+    private final JvmTestKind jvmTestKind;
+    private final String suiteName;
+    private final String className;
+    private final String methodName;
+
+    public DefaultJvmTestOperationDescriptor(String name, String displayName, OperationDescriptor parent, JvmTestKind jvmTestKind, String suiteName, String className, String methodName) {
+        super(name, displayName, parent);
+        this.jvmTestKind = jvmTestKind;
+        this.suiteName = suiteName;
+        this.className = className;
+        this.methodName = methodName;
+    }
+
+    @Override
+    public JvmTestKind getJvmTestKind() {
+        return this.jvmTestKind;
+    }
+
+    @Nullable
+    @Override
+    public String getSuiteName() {
+        return this.suiteName;
+    }
+
+    @Nullable
+    @Override
+    public String getClassName() {
+        return this.className;
+    }
+
+    @Nullable
+    @Override
+    public String getMethodName() {
+        return this.methodName;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFailureResult.java
index 6c8b5f6..edfba3b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFailureResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFailureResult.java
@@ -17,6 +17,7 @@
 package org.gradle.tooling.events.test.internal;
 
 import org.gradle.tooling.Failure;
+import org.gradle.tooling.events.internal.DefaultOperationFailureResult;
 import org.gradle.tooling.events.test.TestFailureResult;
 
 import java.util.List;
@@ -24,31 +25,10 @@ import java.util.List;
 /**
  * Implementation of the {@code TestFailureResult} interface.
  */
-public final class DefaultTestFailureResult implements TestFailureResult {
-
-    private final long startTime;
-    private final long endTime;
-    private final List<? extends Failure> failures;
+public final class DefaultTestFailureResult extends DefaultOperationFailureResult implements TestFailureResult {
 
     public DefaultTestFailureResult(long startTime, long endTime, List<? extends Failure> failures) {
-        this.startTime = startTime;
-        this.endTime = endTime;
-        this.failures = failures;
-    }
-
-    @Override
-    public long getStartTime() {
-        return startTime;
-    }
-
-    @Override
-    public long getEndTime() {
-        return endTime;
-    }
-
-    @Override
-    public List<? extends Failure> getFailures() {
-        return failures;
+        super(startTime, endTime, failures);
     }
 
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFinishEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFinishEvent.java
index c8a6e0a..5983a17 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFinishEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestFinishEvent.java
@@ -16,7 +16,7 @@
 
 package org.gradle.tooling.events.test.internal;
 
-import org.gradle.tooling.events.internal.BaseFinishEvent;
+import org.gradle.tooling.events.internal.DefaultFinishEvent;
 import org.gradle.tooling.events.test.TestFinishEvent;
 import org.gradle.tooling.events.test.TestOperationDescriptor;
 import org.gradle.tooling.events.test.TestOperationResult;
@@ -24,7 +24,7 @@ import org.gradle.tooling.events.test.TestOperationResult;
 /**
  * Implementation of the {@code TestFinishEvent} interface.
  */
-public final class DefaultTestFinishEvent extends BaseFinishEvent implements TestFinishEvent {
+public final class DefaultTestFinishEvent extends DefaultFinishEvent implements TestFinishEvent {
 
     public DefaultTestFinishEvent(long eventTime, String displayName, TestOperationDescriptor descriptor, TestOperationResult result) {
         super(eventTime, displayName, descriptor, result);
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestOperationDescriptor.java
new file mode 100644
index 0000000..1686348
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestOperationDescriptor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.events.test.internal;
+
+import org.gradle.tooling.events.OperationDescriptor;
+import org.gradle.tooling.events.internal.DefaultOperationDescriptor;
+import org.gradle.tooling.events.test.TestOperationDescriptor;
+
+/**
+ * Implementation of the {@code TestOperationDescriptor} interface.
+ */
+public class DefaultTestOperationDescriptor extends DefaultOperationDescriptor implements TestOperationDescriptor {
+
+    public DefaultTestOperationDescriptor(String name, String displayName, OperationDescriptor parent) {
+        super(name, displayName, parent);
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestStartEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestStartEvent.java
index bfee1ad..6f8e0d2 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestStartEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestStartEvent.java
@@ -16,14 +16,14 @@
 
 package org.gradle.tooling.events.test.internal;
 
-import org.gradle.tooling.events.internal.BaseStartEvent;
+import org.gradle.tooling.events.internal.DefaultStartEvent;
 import org.gradle.tooling.events.test.TestOperationDescriptor;
 import org.gradle.tooling.events.test.TestStartEvent;
 
 /**
  * Implementation of the {@code TestStartEvent} interface.
  */
-public final class DefaultTestStartEvent extends BaseStartEvent implements TestStartEvent {
+public final class DefaultTestStartEvent extends DefaultStartEvent implements TestStartEvent {
 
     public DefaultTestStartEvent(long eventTime, String displayName, TestOperationDescriptor descriptor) {
         super(eventTime, displayName, descriptor);
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestSuccessResult.java
index 96ad6eb..e510987 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestSuccessResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/events/test/internal/DefaultTestSuccessResult.java
@@ -16,29 +16,16 @@
 
 package org.gradle.tooling.events.test.internal;
 
+import org.gradle.tooling.events.internal.DefaultOperationSuccessResult;
 import org.gradle.tooling.events.test.TestSuccessResult;
 
 /**
  * Implementation of the {@code TestSuccessResult} interface.
  */
-public final class DefaultTestSuccessResult implements TestSuccessResult {
-
-    private final long startTime;
-    private final long endTime;
+public final class DefaultTestSuccessResult extends DefaultOperationSuccessResult implements TestSuccessResult {
 
     public DefaultTestSuccessResult(long startTime, long endTime) {
-        this.startTime = startTime;
-        this.endTime = endTime;
-    }
-
-    @Override
-    public long getStartTime() {
-        return startTime;
-    }
-
-    @Override
-    public long getEndTime() {
-        return endTime;
+        super(startTime, endTime);
     }
 
 }
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
index 66cf4bb..0b1e2c7 100644
--- 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
@@ -19,12 +19,14 @@ import com.google.common.base.Preconditions;
 import org.gradle.tooling.CancellationToken;
 import org.gradle.tooling.LongRunningOperation;
 import org.gradle.tooling.ProgressListener;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.OperationType;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.EnumSet;
+import java.util.Set;
 
 public abstract class AbstractLongRunningOperation<T extends AbstractLongRunningOperation<T>> implements LongRunningOperation {
     protected final ConnectionParameters connectionParameters;
@@ -43,51 +45,74 @@ public abstract class AbstractLongRunningOperation<T extends AbstractLongRunning
         return operationParamsBuilder.setParameters(connectionParameters).build();
     }
 
+    @Override
     public T withArguments(String... arguments) {
         operationParamsBuilder.setArguments(arguments);
         return getThis();
     }
 
+    @Override
     public T setStandardOutput(OutputStream outputStream) {
         operationParamsBuilder.setStdout(outputStream);
         return getThis();
     }
 
+    @Override
     public T setStandardError(OutputStream outputStream) {
         operationParamsBuilder.setStderr(outputStream);
         return getThis();
     }
 
+    @Override
     public T setStandardInput(InputStream inputStream) {
         operationParamsBuilder.setStdin(inputStream);
         return getThis();
     }
 
+    @Override
     public T setColorOutput(boolean colorOutput) {
         operationParamsBuilder.setColorOutput(colorOutput);
         return getThis();
     }
 
+    @Override
     public T setJavaHome(File javaHome) {
         operationParamsBuilder.setJavaHome(javaHome);
         return getThis();
     }
 
+    @Override
     public T setJvmArguments(String... jvmArguments) {
         operationParamsBuilder.setJvmArguments(jvmArguments);
         return getThis();
     }
 
+    @Override
     public T addProgressListener(ProgressListener listener) {
         operationParamsBuilder.addProgressListener(listener);
         return getThis();
     }
 
-    public T addTestProgressListener(TestProgressListener listener) {
-        operationParamsBuilder.addTestProgressListener(listener);
+    @Override
+    public T addProgressListener(org.gradle.tooling.events.ProgressListener listener) {
+        return addProgressListener(listener, EnumSet.allOf(OperationType.class));
+    }
+
+    @Override
+    public T addProgressListener(org.gradle.tooling.events.ProgressListener listener, Set<OperationType> eventTypes) {
+        if (eventTypes.contains(OperationType.TEST)) {
+            operationParamsBuilder.addTestProgressListener(listener);
+        }
+        if (eventTypes.contains(OperationType.TASK)) {
+            operationParamsBuilder.addTaskProgressListener(listener);
+        }
+        if (eventTypes.contains(OperationType.GENERIC)) {
+            operationParamsBuilder.addBuildOperationProgressListeners(listener);
+        }
         return getThis();
     }
 
+    @Override
     public T withCancellationToken(CancellationToken cancellationToken) {
         operationParamsBuilder.setCancellationToken(Preconditions.checkNotNull(cancellationToken));
         return getThis();
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
index a63dcb6..5559745 100644
--- 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
@@ -54,7 +54,9 @@ class DefaultBuildActionExecuter<T> extends AbstractLongRunningOperation<Default
                            }
 
                            public T run(ConsumerConnection connection) {
-                               return connection.run(buildAction, operationParameters);
+                               T result = connection.run(buildAction, operationParameters);
+                               operationParameters.getBuildProgressListener().rethrowErrors();
+                               return result;
                            }
                        }, new ResultHandlerAdapter<T>(handler) {
                            @Override
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 ec84d9e..f37a417 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
@@ -79,7 +79,9 @@ class DefaultBuildLauncher extends AbstractLongRunningOperation<DefaultBuildLaun
             }
 
             public Void run(ConsumerConnection connection) {
-                return connection.run(Void.class, operationParameters);
+                Void sink = connection.run(Void.class, operationParameters);
+                operationParameters.getBuildProgressListener().rethrowErrors();
+                return sink;
             }
         }, new ResultHandlerAdapter(handler));
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultCancellationTokenSource.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultCancellationTokenSource.java
index 05b8f08..fa765b3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultCancellationTokenSource.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultCancellationTokenSource.java
@@ -29,7 +29,7 @@ public final class DefaultCancellationTokenSource implements CancellationTokenSo
     }
 
     public void cancel() {
-        tokenImpl.token.doCancel();
+        tokenImpl.token.cancel();
     }
 
     public CancellationToken token() {
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 4283bf2..d273594 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
@@ -57,7 +57,9 @@ public class DefaultModelBuilder<T> extends AbstractLongRunningOperation<Default
             }
 
             public T run(ConsumerConnection connection) {
-                return connection.run(modelType, operationParameters);
+                T model = connection.run(modelType, operationParameters);
+                operationParameters.getBuildProgressListener().rethrowErrors();
+                return model;
             }
         }, new ResultHandlerAdapter<T>(handler));
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
index 82beb53..8a268fd 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
@@ -15,10 +15,8 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.tooling.BuildCancelledException;
-import org.gradle.tooling.BuildException;
-import org.gradle.tooling.GradleConnectionException;
-import org.gradle.tooling.ResultHandler;
+import org.gradle.internal.event.ListenerNotificationException;
+import org.gradle.tooling.*;
 import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
 import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
@@ -55,6 +53,8 @@ abstract class ResultHandlerAdapter<T> implements ResultHandlerVersion1<T> {
             handler.onFailure(new BuildCancelledException(connectionFailureMessage(failure), failure.getCause()));
         } else if (failure instanceof BuildExceptionVersion1) {
             handler.onFailure(new BuildException(connectionFailureMessage(failure), failure.getCause()));
+        } else if (failure instanceof ListenerNotificationException) {
+            handler.onFailure(new ListenerFailedException(connectionFailureMessage(failure), ((ListenerNotificationException) failure).getCauses()));
         } else {
             handler.onFailure(new GradleConnectionException(connectionFailureMessage(failure), failure));
         }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapter.java
index 94b67a0..66b1af0 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapter.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapter.java
@@ -17,36 +17,74 @@ package org.gradle.tooling.internal.consumer.parameters;
 
 import org.gradle.internal.event.ListenerBroadcast;
 import org.gradle.tooling.Failure;
+import org.gradle.tooling.events.*;
+import org.gradle.tooling.events.internal.*;
+import org.gradle.tooling.events.task.*;
+import org.gradle.tooling.events.task.internal.*;
 import org.gradle.tooling.events.test.*;
 import org.gradle.tooling.events.test.internal.*;
 import org.gradle.tooling.internal.consumer.DefaultFailure;
-import org.gradle.tooling.internal.protocol.*;
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener;
+import org.gradle.tooling.internal.protocol.InternalFailure;
 import org.gradle.tooling.internal.protocol.events.*;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Converts progress events sent from the tooling provider to the tooling client to the corresponding event types available on the public Tooling API, and broadcasts the converted events to the
  * matching progress listeners. This adapter handles all the different incoming progress event types (except the original logging-derived progress listener).
  */
-class BuildProgressListenerAdapter implements InternalBuildProgressListener {
-
-    private final ListenerBroadcast<TestProgressListener> testProgressListeners = new ListenerBroadcast<TestProgressListener>(TestProgressListener.class);
-    private final Map<Object, TestOperationDescriptor> testDescriptorCache = new HashMap<Object, TestOperationDescriptor>();
-
-    BuildProgressListenerAdapter(List<TestProgressListener> testListeners) {
-        this.testProgressListeners.addAll(testListeners);
+public class BuildProgressListenerAdapter implements InternalBuildProgressListener {
+
+    private final ListenerBroadcast<ProgressListener> testProgressListeners = new ListenerBroadcast<ProgressListener>(ProgressListener.class);
+    private final ListenerBroadcast<ProgressListener> taskProgressListeners = new ListenerBroadcast<ProgressListener>(ProgressListener.class);
+    private final ListenerBroadcast<ProgressListener> buildOperationProgressListeners = new ListenerBroadcast<ProgressListener>(ProgressListener.class);
+    private final Map<Object, OperationDescriptor> descriptorCache = new HashMap<Object, OperationDescriptor>();
+
+    BuildProgressListenerAdapter(List<ProgressListener> testProgressListeners,
+            List<ProgressListener> taskProgressListeners,
+            List<ProgressListener> buildOperationProgressListeners) {
+        this.testProgressListeners.addAll(testProgressListeners);
+        this.taskProgressListeners.addAll(taskProgressListeners);
+        this.buildOperationProgressListeners.addAll(buildOperationProgressListeners);
     }
 
     @Override
     public List<String> getSubscribedOperations() {
-        return this.testProgressListeners.isEmpty() ? Collections.<String>emptyList() : Collections.singletonList(InternalBuildProgressListener.TEST_EXECUTION);
+        List<String> operations = new ArrayList<String>();
+        if (!testProgressListeners.isEmpty()) {
+            operations.add(InternalBuildProgressListener.TEST_EXECUTION);
+        }
+        if (!taskProgressListeners.isEmpty()) {
+            operations.add(InternalBuildProgressListener.TASK_EXECUTION);
+        }
+        if (!buildOperationProgressListeners.isEmpty()) {
+            operations.add(InternalBuildProgressListener.BUILD_EXECUTION);
+        }
+        return operations;
     }
 
     @Override
-    public void onEvent(final Object event) {
+    public void onEvent(Object event) {
+        doBroadcast(event);
+    }
+
+    private void doBroadcast(Object event) {
         if (event instanceof InternalTestProgressEvent) {
-            broadcastTestProgressEvent((InternalTestProgressEvent) event);
+            // Special case for events defined prior to InternalBuildProgressEvent
+            InternalTestProgressEvent progressEvent = (InternalTestProgressEvent) event;
+            broadcastTestProgressEvent(progressEvent);
+        } else if (event instanceof InternalProgressEvent) {
+            InternalProgressEvent progressEvent = (InternalProgressEvent) event;
+            if (progressEvent.getDescriptor() instanceof InternalTaskDescriptor) {
+                broadcastTaskProgressEvent(progressEvent);
+            } else {
+                // Everything else treat as a generic operation
+                broadcastProgressEvent(progressEvent);
+            }
         }
     }
 
@@ -57,7 +95,21 @@ class BuildProgressListenerAdapter implements InternalBuildProgressListener {
         }
     }
 
-    private synchronized TestProgressEvent toTestProgressEvent(final InternalTestProgressEvent event) {
+    private void broadcastTaskProgressEvent(InternalProgressEvent event) {
+        TaskProgressEvent taskProgressEvent = toTaskProgressEvent(event);
+        if (taskProgressEvent != null) {
+            taskProgressListeners.getSource().statusChanged(taskProgressEvent);
+        }
+    }
+
+    private void broadcastProgressEvent(InternalProgressEvent event) {
+        ProgressEvent progressEvent = toProgressEvent(event);
+        if (progressEvent != null) {
+            buildOperationProgressListeners.getSource().statusChanged(progressEvent);
+        }
+    }
+
+    private TestProgressEvent toTestProgressEvent(InternalTestProgressEvent event) {
         if (event instanceof InternalTestStartedProgressEvent) {
             return testStartedEvent((InternalTestStartedProgressEvent) event);
         } else if (event instanceof InternalTestFinishedProgressEvent) {
@@ -67,176 +119,174 @@ class BuildProgressListenerAdapter implements InternalBuildProgressListener {
         }
     }
 
+    private TaskProgressEvent toTaskProgressEvent(InternalProgressEvent event) {
+        if (event instanceof InternalOperationStartedProgressEvent) {
+            return taskStartedEvent((InternalOperationStartedProgressEvent) event);
+        } else if (event instanceof InternalOperationFinishedProgressEvent) {
+            return taskFinishedEvent((InternalOperationFinishedProgressEvent) event);
+        } else {
+            return null;
+        }
+    }
+
+    private ProgressEvent toProgressEvent(InternalProgressEvent event) {
+        if (event instanceof InternalOperationStartedProgressEvent) {
+            return startedEvent((InternalOperationStartedProgressEvent) event);
+        } else if (event instanceof InternalOperationFinishedProgressEvent) {
+            return finishedEvent((InternalOperationFinishedProgressEvent) event);
+        } else {
+            return null;
+        }
+    }
+
     private TestStartEvent testStartedEvent(InternalTestStartedProgressEvent event) {
-        long eventTime = event.getEventTime();
-        String displayName = event.getDisplayName();
-        TestOperationDescriptor testDescriptor = addTestDescriptor(event.getDescriptor());
-        return new DefaultTestStartEvent(eventTime, displayName, testDescriptor);
+        TestOperationDescriptor testDescriptor = addDescriptor(event.getDescriptor(), toTestDescriptor(event.getDescriptor()));
+        return new DefaultTestStartEvent(event.getEventTime(), event.getDisplayName(), testDescriptor);
+    }
+
+    private TaskStartEvent taskStartedEvent(InternalOperationStartedProgressEvent event) {
+        TaskOperationDescriptor descriptor = addDescriptor(event.getDescriptor(), toTaskDescriptor((InternalTaskDescriptor) event.getDescriptor()));
+        return new DefaultTaskStartEvent(event.getEventTime(), event.getDisplayName(), descriptor);
+    }
+
+    private StartEvent startedEvent(InternalOperationStartedProgressEvent event) {
+        OperationDescriptor descriptor = addDescriptor(event.getDescriptor(), toDescriptor(event.getDescriptor()));
+        return new DefaultStartEvent(event.getEventTime(), event.getDisplayName(), descriptor);
     }
 
-    private TestFinishEvent testFinishedEvent(final InternalTestFinishedProgressEvent event) {
-        long eventTime = event.getEventTime();
-        String displayName = event.getDisplayName();
-        TestOperationDescriptor testDescriptor = removeTestDescriptor(event.getDescriptor());
-        TestOperationResult result = toTestResult(event.getResult());
-        return new DefaultTestFinishEvent(eventTime, displayName, testDescriptor, result);
+    private TestFinishEvent testFinishedEvent(InternalTestFinishedProgressEvent event) {
+        TestOperationDescriptor descriptor = removeDescriptor(TestOperationDescriptor.class, event.getDescriptor());
+        return new DefaultTestFinishEvent(event.getEventTime(), event.getDisplayName(), descriptor, toTestResult(event.getResult()));
     }
 
-    private TestOperationDescriptor addTestDescriptor(InternalTestDescriptor testDescriptor) {
-        TestOperationDescriptor cachedTestDescriptor = this.testDescriptorCache.get(testDescriptor.getId());
-        if (cachedTestDescriptor != null) {
-            throw new IllegalStateException(String.format("Operation %s already available.", toString(testDescriptor)));
+    private TaskFinishEvent taskFinishedEvent(InternalOperationFinishedProgressEvent event) {
+        TaskOperationDescriptor descriptor = removeDescriptor(TaskOperationDescriptor.class, event.getDescriptor());
+        return new DefaultTaskFinishEvent(event.getEventTime(), event.getDisplayName(), descriptor, toTaskResult((InternalTaskResult) event.getResult()));
+    }
+
+    private FinishEvent finishedEvent(InternalOperationFinishedProgressEvent event) {
+        OperationDescriptor descriptor = removeDescriptor(OperationDescriptor.class, event.getDescriptor());
+        return new DefaultFinishEvent(event.getEventTime(), event.getDisplayName(), descriptor, toResult(event.getResult()));
+    }
+
+    private synchronized <T extends OperationDescriptor> T addDescriptor(InternalOperationDescriptor descriptor, T clientDescriptor) {
+        OperationDescriptor cached = this.descriptorCache.get(descriptor.getId());
+        if (cached != null) {
+            throw new IllegalStateException(String.format("Operation %s already available.", descriptor));
         }
-        final TestOperationDescriptor parent = getParentTestDescriptor(testDescriptor);
-        TestOperationDescriptor newTestDescriptor = toTestDescriptor(testDescriptor, parent);
-        testDescriptorCache.put(testDescriptor.getId(), newTestDescriptor);
-        return newTestDescriptor;
+        descriptorCache.put(descriptor.getId(), clientDescriptor);
+        return clientDescriptor;
     }
 
-    private TestOperationDescriptor removeTestDescriptor(InternalTestDescriptor testDescriptor) {
-        TestOperationDescriptor cachedTestDescriptor = this.testDescriptorCache.remove(testDescriptor.getId());
+    private synchronized <T extends OperationDescriptor> T removeDescriptor(Class<T> type, InternalOperationDescriptor descriptor) {
+        OperationDescriptor cachedTestDescriptor = this.descriptorCache.remove(descriptor.getId());
         if (cachedTestDescriptor == null) {
-            throw new IllegalStateException(String.format("Operation %s is not available.", toString(testDescriptor)));
-        }
-        return cachedTestDescriptor;
-    }
-
-    private static TestOperationDescriptor toTestDescriptor(final InternalTestDescriptor testDescriptor, final TestOperationDescriptor parent) {
-        if (testDescriptor instanceof InternalJvmTestDescriptor) {
-            final InternalJvmTestDescriptor jvmTestDescriptor = (InternalJvmTestDescriptor) testDescriptor;
-            return new JvmTestOperationDescriptor() {
-                @Override
-                public String getName() {
-                    return jvmTestDescriptor.getName();
-                }
-
-                @Override
-                public String getDisplayName() {
-                    return jvmTestDescriptor.getDisplayName();
-                }
-
-                @Override
-                public JvmTestKind getJvmTestKind() {
-                    return toJvmTestKind(jvmTestDescriptor);
-                }
-
-                @Override
-                public String getSuiteName() {
-                    return jvmTestDescriptor.getSuiteName();
-                }
-
-                @Override
-                public String getClassName() {
-                    return jvmTestDescriptor.getClassName();
-                }
-
-                @Override
-                public String getMethodName() {
-                    return jvmTestDescriptor.getMethodName();
-                }
-
-                @Override
-                public TestOperationDescriptor getParent() {
-                    return parent;
-                }
-
-                @Override
-                public String toString() {
-                    return getDisplayName();
-                }
-            };
-        } else {
-            return new TestOperationDescriptor() {
-                @Override
-                public String getName() {
-                    return testDescriptor.getName();
-                }
-
-                @Override
-                public String getDisplayName() {
-                    return testDescriptor.getDisplayName();
-                }
+            throw new IllegalStateException(String.format("Operation %s is not available.", descriptor));
+        }
+        return assertDescriptorType(type, cachedTestDescriptor);
+    }
 
-                @Override
-                public TestOperationDescriptor getParent() {
-                    return parent;
-                }
+    private <T extends OperationDescriptor> T assertDescriptorType(Class<T> type, OperationDescriptor descriptor) {
+        Class<? extends OperationDescriptor> descriptorClass = descriptor.getClass();
+        if (!type.isAssignableFrom(descriptorClass)) {
+            throw new IllegalStateException(String.format("Unexpected operation type. Required %s but found %s", type.getName(), descriptorClass.getName()));
+        }
+        return (T) descriptor;
+    }
 
-                @Override
-                public String toString() {
-                    return getDisplayName();
-                }
-            };
+    private TestOperationDescriptor toTestDescriptor(InternalTestDescriptor descriptor) {
+        OperationDescriptor parent = getParentDescriptor(descriptor.getParentId());
+        if (descriptor instanceof InternalJvmTestDescriptor) {
+            InternalJvmTestDescriptor jvmTestDescriptor = (InternalJvmTestDescriptor) descriptor;
+            return new DefaultJvmTestOperationDescriptor(descriptor.getName(), descriptor.getDisplayName(), parent,
+                toJvmTestKind(jvmTestDescriptor.getTestKind()), jvmTestDescriptor.getSuiteName(), jvmTestDescriptor.getClassName(), jvmTestDescriptor.getMethodName());
+        } else {
+            return new DefaultTestOperationDescriptor(descriptor.getName(), descriptor.getDisplayName(), parent);
         }
     }
 
-    private static JvmTestKind toJvmTestKind(InternalJvmTestDescriptor jvmTestDescriptor) {
-        String jvmTestKind = jvmTestDescriptor.getTestKind();
-        if (InternalJvmTestDescriptor.KIND_SUITE.equals(jvmTestKind)) {
+    private static JvmTestKind toJvmTestKind(String testKind) {
+        if (InternalJvmTestDescriptor.KIND_SUITE.equals(testKind)) {
             return JvmTestKind.SUITE;
-        } else if (InternalJvmTestDescriptor.KIND_ATOMIC.equals(jvmTestKind)) {
+        } else if (InternalJvmTestDescriptor.KIND_ATOMIC.equals(testKind)) {
             return JvmTestKind.ATOMIC;
         } else {
             return JvmTestKind.UNKNOWN;
         }
     }
 
-    private TestOperationResult toTestResult(final InternalTestResult result) {
+    private TaskOperationDescriptor toTaskDescriptor(InternalTaskDescriptor descriptor) {
+        OperationDescriptor parent = getParentDescriptor(descriptor.getParentId());
+        return new DefaultTaskOperationDescriptor(descriptor.getName(), descriptor.getDisplayName(), descriptor.getTaskPath(), parent);
+    }
+
+    private OperationDescriptor toDescriptor(InternalOperationDescriptor descriptor) {
+        OperationDescriptor parent = getParentDescriptor(descriptor.getParentId());
+        return new DefaultOperationDescriptor(descriptor.getName(), descriptor.getDisplayName(), parent);
+    }
+
+    private synchronized OperationDescriptor getParentDescriptor(Object parentId) {
+        if (parentId == null) {
+            return null;
+        } else {
+            OperationDescriptor operationDescriptor = descriptorCache.get(parentId);
+            if (operationDescriptor == null) {
+                throw new IllegalStateException(String.format("Parent operation with id %s not available.", parentId));
+            } else {
+                return operationDescriptor;
+            }
+        }
+    }
+
+    private TestOperationResult toTestResult(InternalTestResult result) {
         if (result instanceof InternalTestSuccessResult) {
             return new DefaultTestSuccessResult(result.getStartTime(), result.getEndTime());
         } else if (result instanceof InternalTestSkippedResult) {
             return new DefaultTestSkippedResult(result.getStartTime(), result.getEndTime());
         } else if (result instanceof InternalTestFailureResult) {
-            return new DefaultTestFailureResult(result.getStartTime(), result.getEndTime(), toFailures(result));
+            return new DefaultTestFailureResult(result.getStartTime(), result.getEndTime(), toFailures(result.getFailures()));
         } else {
             return null;
         }
     }
 
-    private static List<Failure> toFailures(InternalTestResult testResult) {
-        List<Failure> failures = new ArrayList<Failure>();
-        for (InternalFailure origFailure : testResult.getFailures()) {
-            failures.add(toFailure(origFailure));
+    private static TaskOperationResult toTaskResult(InternalTaskResult result) {
+        if (result instanceof InternalTaskSuccessResult) {
+            return new DefaultTaskSuccessResult(result.getStartTime(), result.getEndTime(), ((InternalTaskSuccessResult) result).isUpToDate());
+        } else if (result instanceof InternalTaskSkippedResult) {
+            return new DefaultTaskSkippedResult(result.getStartTime(), result.getEndTime(), ((InternalTaskSkippedResult) result).getSkipMessage());
+        } else if (result instanceof InternalTaskFailureResult) {
+            return new DefaultTaskFailureResult(result.getStartTime(), result.getEndTime(), toFailures(result.getFailures()));
+        } else {
+            return null;
         }
-        return failures;
-    }
-
-    private static Failure toFailure(InternalFailure origFailure) {
-        return origFailure == null ? null : new DefaultFailure(
-                origFailure.getMessage(),
-                origFailure.getDescription(),
-                toFailure(origFailure.getCauses()));
     }
 
-    private static List<Failure> toFailure(List<? extends InternalFailure> causes) {
-        List<Failure> result = new ArrayList<Failure>();
-        for (InternalFailure cause : causes) {
-            result.add(toFailure(cause));
+    private static OperationResult toResult(InternalOperationResult result) {
+        if (result instanceof InternalSuccessResult) {
+            return new DefaultOperationSuccessResult(result.getStartTime(), result.getEndTime());
+        } else if (result instanceof InternalFailureResult) {
+            return new DefaultOperationFailureResult(result.getStartTime(), result.getEndTime(), toFailures(result.getFailures()));
+        } else {
+            return null;
         }
-        return result;
     }
 
-    private TestOperationDescriptor getParentTestDescriptor(InternalTestDescriptor testDescriptor) {
-        Object parentId = testDescriptor.getParentId();
-        if (parentId == null) {
+    private static List<Failure> toFailures(List<? extends InternalFailure> causes) {
+        if (causes == null) {
             return null;
-        } else {
-            TestOperationDescriptor parentTestDescriptor = testDescriptorCache.get(parentId);
-            if (parentTestDescriptor == null) {
-                throw new IllegalStateException(String.format("Parent test descriptor with id %s not available for %s.", parentId, toString(testDescriptor)));
-            } else {
-                return parentTestDescriptor;
-            }
         }
-    }
-
-    private static String toString(InternalTestDescriptor testDescriptor) {
-        if (testDescriptor instanceof InternalJvmTestDescriptor) {
-            return String.format("TestOperationDescriptor[id(%s), name(%s), className(%s), parent(%s)]",
-                    testDescriptor.getId(), testDescriptor.getName(), ((InternalJvmTestDescriptor) testDescriptor).getClassName(), testDescriptor.getParentId());
-        } else {
-            return String.format("TestOperationDescriptor[id(%s), name(%s), parent(%s)]", testDescriptor.getId(), testDescriptor.getName(), testDescriptor.getParentId());
+        List<Failure> failures = new ArrayList<Failure>();
+        for (InternalFailure cause : causes) {
+            failures.add(toFailure(cause));
         }
+        return failures;
     }
 
+    private static Failure toFailure(InternalFailure origFailure) {
+        return origFailure == null ? null : new DefaultFailure(
+            origFailure.getMessage(),
+            origFailure.getDescription(),
+            toFailures(origFailure.getCauses()));
+    }
 }
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 c223b76..4170664 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParameters.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParameters.java
@@ -19,8 +19,7 @@ import com.google.common.collect.Lists;
 import org.gradle.api.GradleException;
 import org.gradle.initialization.BuildCancellationToken;
 import org.gradle.tooling.CancellationToken;
-import org.gradle.tooling.ProgressListener;
-import org.gradle.tooling.events.test.TestProgressListener;
+import org.gradle.tooling.events.ProgressListener;
 import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter;
 import org.gradle.tooling.internal.consumer.CancellationTokenInternal;
 import org.gradle.tooling.internal.consumer.ConnectionParameters;
@@ -42,8 +41,10 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
     }
 
     public static class Builder {
-        private final List<ProgressListener> progressListeners = new ArrayList<ProgressListener>();
-        private final List<TestProgressListener> testProgressListeners = new ArrayList<TestProgressListener>();
+        private final List<org.gradle.tooling.ProgressListener> legacyProgressListeners = new ArrayList<org.gradle.tooling.ProgressListener>();
+        private final List<ProgressListener> testProgressListeners = new ArrayList<ProgressListener>();
+        private final List<ProgressListener> taskProgressListeners = new ArrayList<ProgressListener>();
+        private final List<ProgressListener> buildOperationProgressListeners = new ArrayList<ProgressListener>();
         private CancellationToken cancellationToken;
         private ConnectionParameters parameters;
         private OutputStream stdout;
@@ -121,7 +122,7 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
                     taskPaths.add(((Task) launchable).getPath());
                 } else {
                     throw new GradleException("Only Task or TaskSelector instances are supported: "
-                            + (launchable != null ? launchable.getClass() : "null"));
+                        + (launchable != null ? launchable.getClass() : "null"));
                 }
             }
             this.launchables = launchablesParams;
@@ -129,14 +130,22 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
             return this;
         }
 
-        public void addProgressListener(ProgressListener listener) {
-            progressListeners.add(listener);
+        public void addProgressListener(org.gradle.tooling.ProgressListener listener) {
+            legacyProgressListeners.add(listener);
         }
 
-        public void addTestProgressListener(TestProgressListener listener) {
+        public void addTestProgressListener(ProgressListener listener) {
             testProgressListeners.add(listener);
         }
 
+        public void addTaskProgressListener(ProgressListener listener) {
+            taskProgressListeners.add(listener);
+        }
+
+        public void addBuildOperationProgressListeners(ProgressListener listener) {
+            buildOperationProgressListeners.add(listener);
+        }
+
         public void setCancellationToken(CancellationToken cancellationToken) {
             this.cancellationToken = cancellationToken;
         }
@@ -145,15 +154,16 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
             // create the listener adapters right when the ConsumerOperationParameters are instantiated but no earlier,
             // this ensures that when multiple requests are issued that are built from the same builder, such requests do not share any state kept in the listener adapters
             // e.g. if the listener adapters do per-request caching, such caching must not leak between different requests built from the same builder
-            ProgressListenerAdapter progressListenerAdapter = new ProgressListenerAdapter(this.progressListeners);
-            BuildProgressListenerAdapter testProgressListenerAdapter = new BuildProgressListenerAdapter(this.testProgressListeners);
+            ProgressListenerAdapter progressListenerAdapter = new ProgressListenerAdapter(this.legacyProgressListeners);
+            FailsafeBuildProgressListenerAdapter buildProgressListenerAdapter = new FailsafeBuildProgressListenerAdapter(
+                new BuildProgressListenerAdapter(this.testProgressListeners, this.taskProgressListeners, this.buildOperationProgressListeners));
             return new ConsumerOperationParameters(parameters, stdout, stderr, colorOutput, stdin, javaHome, jvmArguments, arguments, tasks, launchables,
-                    progressListenerAdapter, testProgressListenerAdapter, cancellationToken);
+                progressListenerAdapter, buildProgressListenerAdapter, cancellationToken);
         }
     }
 
     private final ProgressListenerAdapter progressListener;
-    private final BuildProgressListenerAdapter buildProgressListener;
+    private final FailsafeBuildProgressListenerAdapter buildProgressListener;
     private final CancellationToken cancellationToken;
     private final ConnectionParameters parameters;
     private final long startTime = System.currentTimeMillis();
@@ -171,7 +181,7 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
 
     private ConsumerOperationParameters(ConnectionParameters parameters, OutputStream stdout, OutputStream stderr, Boolean colorOutput, InputStream stdin,
                                         File javaHome, List<String> jvmArguments, List<String> arguments, List<String> tasks, List<InternalLaunchable> launchables,
-                                        ProgressListenerAdapter progressListener, BuildProgressListenerAdapter buildProgressListener, CancellationToken cancellationToken) {
+                                        ProgressListenerAdapter progressListener, FailsafeBuildProgressListenerAdapter buildProgressListener, CancellationToken cancellationToken) {
         this.parameters = parameters;
         this.stdout = stdout;
         this.stderr = stderr;
@@ -276,7 +286,7 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
         return progressListener;
     }
 
-    public InternalBuildProgressListener getBuildProgressListener() {
+    public FailsafeBuildProgressListenerAdapter getBuildProgressListener() {
         return buildProgressListener;
     }
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/FailsafeBuildProgressListenerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/FailsafeBuildProgressListenerAdapter.java
new file mode 100644
index 0000000..d792b1f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/FailsafeBuildProgressListenerAdapter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.internal.event.ListenerNotificationException;
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener;
+
+import java.util.Collections;
+import java.util.List;
+
+public class FailsafeBuildProgressListenerAdapter implements InternalBuildProgressListener {
+    private final InternalBuildProgressListener delegate;
+    private Throwable listenerFailure;
+
+    public FailsafeBuildProgressListenerAdapter(InternalBuildProgressListener delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void onEvent(Object event) {
+        if (listenerFailure != null) {
+            // Discard event
+            return;
+        }
+        try {
+            delegate.onEvent(event);
+        } catch (Throwable t) {
+            listenerFailure = t;
+        }
+    }
+
+    @Override
+    public List<String> getSubscribedOperations() {
+        return delegate.getSubscribedOperations();
+    }
+
+    public void rethrowErrors() {
+        if (listenerFailure != null) {
+            throw new ListenerNotificationException("One or more progress listeners failed with an exception.", Collections.singletonList(listenerFailure));
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildProgressListener.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildProgressListener.java
index da37500..e02cf42 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildProgressListener.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildProgressListener.java
@@ -28,6 +28,16 @@ public interface InternalBuildProgressListener {
     String TEST_EXECUTION = "TEST_EXECUTION";
 
     /**
+     * The constant for the task execution operations.
+     */
+    String TASK_EXECUTION = "TASK_EXECUTION";
+
+    /**
+     * The constant for the build execution operations.
+     */
+    String BUILD_EXECUTION = "BUILD_EXECUTION";
+
+    /**
      * Invoked when a progress event happens in the build being run, and one or more listeners for the given event type have been registered.
      *
      * The event types implemented in Gradle 2.4 are:
@@ -36,6 +46,13 @@ public interface InternalBuildProgressListener {
      *     <li>{@link org.gradle.tooling.internal.protocol.events.InternalTestProgressEvent}</li>
      * </ul>
      *
+     * The event types implemented in Gradle 2.5 are:
+     *
+     * <ul>
+     *     <li>{@link org.gradle.tooling.internal.protocol.events.InternalProgressEvent} - used for all operation types: task, test and generic.</li>
+     *     <li>{@link org.gradle.tooling.internal.protocol.events.InternalTestProgressEvent} - test events also implement these types for backwards compatibility</li>
+     * </ul>
+     *
      * @param event The issued progress event
      */
     void onEvent(Object event);
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalFailureResult.java
new file mode 100644
index 0000000..f1f9a3a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalFailureResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalFailureResult extends InternalOperationResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationDescriptor.java
new file mode 100644
index 0000000..3f14b32
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationDescriptor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalOperationDescriptor extends InternalProtocolInterface {
+
+    /**
+     * Returns an id that uniquely identifies the operation.
+     */
+    Object getId();
+
+    /**
+     * Returns the name of the operation, relative to its parent. This name should generally uniquely identify to
+     * a human each child operation of a given operation.
+     */
+    String getName();
+
+    /**
+     * Returns a human consumable display name for the operation. This display name should generally provide enough context
+     * to uniquely identify the operation to a human.
+     *
+     * @return The display name of the operation
+     */
+    String getDisplayName();
+
+    /**
+     * Returns the id of the parent of the operation, if any.
+     *
+     * @return The id of the parent of the operation, can be null
+     */
+    Object getParentId();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationFinishedProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationFinishedProgressEvent.java
new file mode 100644
index 0000000..c8f0309
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationFinishedProgressEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalOperationFinishedProgressEvent extends InternalProgressEvent {
+    /**
+     * Returns the result of running the build operation.
+     *
+     * @return The build operation result
+     */
+    InternalOperationResult getResult();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationResult.java
new file mode 100644
index 0000000..52e5a28
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.InternalFailure;
+import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
+
+import java.util.List;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalOperationResult extends InternalProtocolInterface {
+    /**
+     * Returns the time the build execution started.
+     *
+     * @return The start time of the build
+     */
+    long getStartTime();
+
+    /**
+     * Returns the time the result was produced.
+     *
+     * @return The time the result was produced.
+     */
+    long getEndTime();
+
+    /**
+     * Returns the failures that occurred while running the build, if any.
+     *
+     * @return The failures that occurred
+     */
+    List<? extends InternalFailure> getFailures();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationStartedProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationStartedProgressEvent.java
new file mode 100644
index 0000000..fa39ec8
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalOperationStartedProgressEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalOperationStartedProgressEvent extends InternalProgressEvent {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalProgressEvent.java
new file mode 100644
index 0000000..47834bb
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalProgressEvent.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalProgressEvent extends InternalProtocolInterface {
+    /**
+     * Returns the time when the event happened.
+     *
+     * @return The event time
+     */
+    long getEventTime();
+
+    /**
+     * Returns a human consumable display name for this event.
+     */
+    String getDisplayName();
+
+    /**
+     * Returns the description of the operation for which progress is reported.
+     *
+     * @return The build description
+     */
+    InternalOperationDescriptor getDescriptor();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalSuccessResult.java
new file mode 100644
index 0000000..cc57bad
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalSuccessResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalSuccessResult extends InternalOperationResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskDescriptor.java
new file mode 100644
index 0000000..cead936
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskDescriptor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalTaskDescriptor extends InternalOperationDescriptor {
+    /**
+     * Returns the path of the task.
+     *
+     * @return The path of the task
+     */
+    String getTaskPath();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskFailureResult.java
new file mode 100644
index 0000000..70a7f47
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskFailureResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalTaskFailureResult extends InternalTaskResult, InternalFailureResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskResult.java
new file mode 100644
index 0000000..6cf41f0
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalTaskResult extends InternalOperationResult {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSkippedResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSkippedResult.java
new file mode 100644
index 0000000..2e317d5
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSkippedResult.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * Represents a test that was not run.
+ *
+ * @since 2.5
+ */
+public interface InternalTaskSkippedResult extends InternalTaskResult {
+    String getSkipMessage();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSuccessResult.java
new file mode 100644
index 0000000..8c763b3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTaskSuccessResult.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @since 2.5
+ */
+public interface InternalTaskSuccessResult extends InternalTaskResult, InternalSuccessResult {
+    /**
+     * Returns whether this task was up-to-date.
+     *
+     * @return {@code true} if this task was up-to-date
+     */
+    boolean isUpToDate();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestDescriptor.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestDescriptor.java
index 773ee4f..6e5f953 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestDescriptor.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestDescriptor.java
@@ -16,39 +16,10 @@
 
 package org.gradle.tooling.internal.protocol.events;
 
-import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
-
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
  *
  * @since 2.4
  */
-public interface InternalTestDescriptor extends InternalProtocolInterface {
-
-    /**
-     * Returns the id that uniquely identifies the test.
-     *
-     * @return The unique id of the test, never null
-     */
-    Object getId();
-
-    /**
-     * Returns the name of the test.
-     *
-     * @return The name of the test, never null
-     */
-    String getName();
-
-    /**
-     * Returns a human consumable display name for the test.
-     */
-    String getDisplayName();
-
-    /**
-     * Returns the id of the parent of this test, if any.
-     *
-     * @return The id of the parent of this test, can be null
-     */
-    Object getParentId();
-
+public interface InternalTestDescriptor extends InternalOperationDescriptor {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFailureResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFailureResult.java
index 30b4107..48bf5ec 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFailureResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFailureResult.java
@@ -21,5 +21,5 @@ package org.gradle.tooling.internal.protocol.events;
  *
  * @since 2.4
  */
-public interface InternalTestFailureResult extends InternalTestResult {
+public interface InternalTestFailureResult extends InternalTestResult, InternalFailureResult {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFinishedProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFinishedProgressEvent.java
index a0df00a..fd85d8b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFinishedProgressEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestFinishedProgressEvent.java
@@ -21,7 +21,7 @@ package org.gradle.tooling.internal.protocol.events;
  *
  * @since 2.4
  */
-public interface InternalTestFinishedProgressEvent extends InternalTestProgressEvent {
+public interface InternalTestFinishedProgressEvent extends InternalTestProgressEvent, InternalOperationFinishedProgressEvent {
     /**
      * Returns the result of running the test.
      *
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestProgressEvent.java
index 4d3c0c4..da2aaa4 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestProgressEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestProgressEvent.java
@@ -15,26 +15,12 @@
  */
 package org.gradle.tooling.internal.protocol.events;
 
-import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
-
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
  *
  * @since 2.4
  */
-public interface InternalTestProgressEvent extends InternalProtocolInterface {
-    /**
-     * Returns the time when the event happened.
-     *
-     * @return The event time
-     */
-    long getEventTime();
-
-    /**
-     * Returns a human consumable display name for this event.
-     */
-    String getDisplayName();
-
+public interface InternalTestProgressEvent extends InternalProgressEvent {
     /**
      * Returns the description of the test for which progress is reported.
      *
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestResult.java
index b6d3ff8..ccf8fa5 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestResult.java
@@ -16,36 +16,10 @@
 
 package org.gradle.tooling.internal.protocol.events;
 
-import org.gradle.tooling.internal.protocol.InternalFailure;
-import org.gradle.tooling.internal.protocol.InternalProtocolInterface;
-
-import java.util.List;
-
 /**
  * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
  *
  * @since 2.4
  */
-public interface InternalTestResult extends InternalProtocolInterface {
-    /**
-     * Returns the time the test execution started.
-     *
-     * @return The start time
-     */
-    long getStartTime();
-
-    /**
-     * Returns the time the test execution finished.
-     *
-     * @return The finish time
-     */
-    long getEndTime();
-
-    /**
-     * Returns the failures that occurred while running the test, if any.
-     *
-     * @return The failures that occurred
-     */
-    List<? extends InternalFailure> getFailures();
-
+public interface InternalTestResult extends InternalOperationResult {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestStartedProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestStartedProgressEvent.java
index ac314e1..1f9c639 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestStartedProgressEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestStartedProgressEvent.java
@@ -17,7 +17,9 @@
 package org.gradle.tooling.internal.protocol.events;
 
 /**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
  * @since 2.4
  */
-public interface InternalTestStartedProgressEvent extends InternalTestProgressEvent {
+public interface InternalTestStartedProgressEvent extends InternalTestProgressEvent, InternalOperationStartedProgressEvent {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestSuccessResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestSuccessResult.java
index ee6cfa9..1974712 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestSuccessResult.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/events/InternalTestSuccessResult.java
@@ -21,5 +21,5 @@ package org.gradle.tooling.internal.protocol.events;
  *
  * @since 2.4
  */
-public interface InternalTestSuccessResult extends InternalTestResult {
+public interface InternalTestSuccessResult extends InternalTestResult, InternalSuccessResult {
 }
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 b2cc4e2..2492cb9 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
@@ -48,6 +48,14 @@ public interface ExternalDependency extends Dependency {
     File getJavadoc();
 
     /**
+     * Marks this dependency as exported.
+     *
+     * @return whether this dependency needs to be exported.
+     * @since 2.5
+     */
+    boolean isExported();
+
+    /**
      * Returns the Gradle module information for this dependency, or {@code null} if the dependency does not
      * originate from a remote repository.
      *
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 8e6cc37..75792bf 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.tooling.model;
 
+import org.gradle.api.Incubating;
 import org.gradle.api.Nullable;
 
 /**
@@ -60,4 +61,14 @@ public interface Task extends Launchable {
      */
     @Deprecated
     Element getProject();
+
+    /**
+     * Returns the group a task belongs to.
+     *
+     * @return the group a task belongs to.
+     * @since 2.5
+     */
+    @Incubating
+    @Nullable
+    String getGroup();
 }
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 784944c..a7ad4d3 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
@@ -52,8 +52,7 @@ public interface BuildEnvironment extends Model {
      * 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.
+     * @throws UnsupportedMethodException For Gradle versions older than 1.0-milestone-8, where this method is not supported.
      */
     JavaEnvironment getJava() throws UnsupportedMethodException;
 }
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 f76c0de..ea5b351 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
@@ -20,8 +20,7 @@ import java.io.File;
 import java.util.List;
 
 /**
- * Informs about the Java environment, for example the Java home or the JVM args used.
- * See example in {@link BuildEnvironment}.
+ * Informs about the Java environment, for example the Java home or the JVM args used. See example in {@link BuildEnvironment}.
  *
  * @since 1.0-milestone-8
  */
@@ -35,12 +34,12 @@ public interface JavaEnvironment {
     File getJavaHome();
 
     /**
-     * The JVM arguments used to start the Java process that handles Gradle operations
-     * (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".
+     * The JVM arguments the user has provided to start the Java process that handles Gradle operations (for example running tasks or acquiring model information). The returned arguments do not
+     * include system properties passed as -Dfoo=bar. They may include extra properties added by default if no user jvm arguments are specified, like those required by the Gradle daemon (eg.
+     * MaxPermSize).
      *
      * @since 1.0-milestone-8
      */
     List<String> getJvmArguments();
+
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
index 94c1cf0..6b69df2 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
@@ -32,4 +32,12 @@ public interface EclipseProjectDependency extends ProjectDependency {
      * Returns the path to use for this project dependency.
      */
     String getPath();
+
+    /**
+     * Marks this dependency as exported.
+     *
+     * @return whether this dependency needs to be exported.
+     * @since 2.5
+     */
+    boolean isExported();
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/NonCancellableConsumerConnectionAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/NonCancellableConsumerConnectionAdapterTest.groovy
index 37ede69..99e0053 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/NonCancellableConsumerConnectionAdapterTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/NonCancellableConsumerConnectionAdapterTest.groovy
@@ -53,7 +53,7 @@ class NonCancellableConsumerConnectionAdapterTest extends Specification {
 
         given:
         _ * target.run(action, parameters) >> {
-            cancellation.doCancel()
+            cancellation.cancel()
             'result'
         }
 
@@ -77,7 +77,7 @@ class NonCancellableConsumerConnectionAdapterTest extends Specification {
 
         when:
         def result = connection.run(action, parameters)
-        cancellation.doCancel()
+        cancellation.cancel()
 
         then:
         result == 'result'
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForBuildOperationsTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForBuildOperationsTest.groovy
new file mode 100644
index 0000000..0177058
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForBuildOperationsTest.groovy
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events.FinishEvent
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.StartEvent
+import org.gradle.tooling.events.internal.DefaultFinishEvent
+import org.gradle.tooling.events.internal.DefaultOperationFailureResult
+import org.gradle.tooling.events.internal.DefaultOperationSuccessResult
+import org.gradle.tooling.events.internal.DefaultStartEvent
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener
+import org.gradle.tooling.internal.protocol.InternalFailure
+import org.gradle.tooling.internal.protocol.events.*
+import spock.lang.Specification
+
+class BuildProgressListenerAdapterForBuildOperationsTest extends Specification {
+
+    def "adapter is only subscribing to build progress events if at least one build progress listener is attached"() {
+        when:
+        def adapter = createAdapter()
+
+        then:
+        adapter.subscribedOperations == []
+
+        when:
+        def listener = Mock(ProgressListener)
+        adapter = createAdapter(listener)
+
+        then:
+        adapter.subscribedOperations == [InternalBuildProgressListener.BUILD_EXECUTION]
+    }
+
+    def "only BuildProgressEventX instances are processed if a build listener is added"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        adapter.onEvent(Mock(InternalTestProgressEvent))
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "only BuildProgressEventX instances of known type are processed"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def unknownEvent = Mock(InternalProgressEvent)
+        adapter.onEvent(unknownEvent)
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "conversion of start events throws exception if previous start event with same build descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor('id', 'some build')
+        def startEvent = buildStartEvent(999, 'start', buildDesc)
+
+        adapter.onEvent(startEvent)
+        adapter.onEvent(startEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('already available')
+    }
+
+    def "conversion of non-start events throws exception if no previous start event with same build descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(1, 'some build')
+        def finishEvent = buildFinishEvent(999, 'finish', buildDesc)
+
+        adapter.onEvent(finishEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('not available')
+    }
+
+    def "looking up parent operation throws exception if no previous event for parent operation exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def childBuildDescriptor = Mock(InternalOperationDescriptor)
+        _ * childBuildDescriptor.getId() >> 2
+        _ * childBuildDescriptor.getName() >> 'some child'
+        _ * childBuildDescriptor.getParentId() >> 1
+
+        def childEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * childEvent.getDisplayName() >> 'child event'
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childBuildDescriptor
+
+        adapter.onEvent(childEvent)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "conversion of child events expects parent event exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def parentTaskDescriptor = Mock(InternalTaskDescriptor)
+        _ * parentTaskDescriptor.getId() >> 1
+        _ * parentTaskDescriptor.getName() >> 'some parent'
+        _ * parentTaskDescriptor.getParentId() >> null
+
+        def parentEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * parentEvent.getEventTime() >> 999
+        _ * parentEvent.getDescriptor() >> parentTaskDescriptor
+
+        def childTaskDescriptor = Mock(InternalTaskDescriptor)
+        _ * childTaskDescriptor.getId() >> 2
+        _ * childTaskDescriptor.getName() >> 'some child'
+        _ * childTaskDescriptor.getParentId() >> parentTaskDescriptor.getId()
+
+        def childEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childTaskDescriptor
+
+        adapter.onEvent(parentEvent)
+        adapter.onEvent(childEvent)
+
+        then:
+        notThrown(IllegalStateException)
+    }
+
+    def "convert all InternalBuildDescriptor attributes"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(666, 'my build', 'some build')
+        def startEvent = buildStartEvent(999, 'build started', buildDesc)
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as StartEvent) >> { DefaultStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "build started"
+            assert event.descriptor.name == 'my build'
+            assert event.descriptor.displayName == 'some build'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to BuildStartEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(666, 'some build')
+        def startEvent = buildStartEvent(999, 'build started', buildDesc)
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as StartEvent) >> { DefaultStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == 'build started'
+            assert event.descriptor.name == 'some build'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to BuildSucceededEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(666, 'some build')
+        def startEvent = buildStartEvent(999, 'build started', buildDesc)
+
+        def buildResult = buildSuccess(1, 2)
+        def succeededEvent = buildFinishEvent(999, 'build succeeded', buildDesc, buildResult)
+
+        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
+        adapter.onEvent(succeededEvent)
+
+        then:
+        1 * listener.statusChanged(_ as FinishEvent) >> { DefaultFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == 'build succeeded'
+            assert event.descriptor.name == 'some build'
+            assert event.descriptor.parent == null
+            assert event.result instanceof DefaultOperationSuccessResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+        }
+    }
+
+    def "convert to BuildSucceededEvent when settings evaluated"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(666, 'some build')
+        def buildStart = buildStartEvent(999, 'build started', buildDesc)
+        def settingsEvalStart = buildStartEvent(1000, 'settings evaluated', buildDescriptor(667, 'settings evaluated', buildDesc))
+        def settingsEvalEnd = buildFinishEvent(1001, 'settings evaluated', buildDescriptor(667, 'settings evaluated', buildDesc), buildSuccess(999, 1001))
+
+        adapter.onEvent(buildStart) // succeededEvent always assumes a previous startEvent
+        adapter.onEvent(settingsEvalStart)
+        adapter.onEvent(settingsEvalEnd)
+
+        then:
+        1 * listener.statusChanged(_ as StartEvent) >> { DefaultStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == 'build started'
+            assert event.descriptor.name == 'some build'
+            assert event.descriptor.parent == null
+        }
+        1 * listener.statusChanged(_ as StartEvent) >> { DefaultStartEvent event ->
+            assert event.eventTime == 1000
+            assert event.displayName == 'settings evaluated'
+            assert event.descriptor.name == 'settings evaluated'
+            assert event.descriptor.parent.name == 'some build'
+        }
+        1 * listener.statusChanged(_ as FinishEvent) >> { DefaultFinishEvent event ->
+            assert event.eventTime == 1001
+            assert event.displayName == 'settings evaluated'
+            assert event.descriptor.name == 'settings evaluated'
+            assert event.descriptor.parent.name == 'some build'
+            assert event.result instanceof DefaultOperationSuccessResult
+            assert event.result.startTime == 999
+            assert event.result.endTime == 1001
+        }
+    }
+
+    def "convert to BuildFailedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def buildDesc = buildDescriptor(666, 'some build')
+        def startEvent = buildStartEvent(999, 'start build', buildDesc)
+
+        def buildResult = buildFailure(1, 2, Stub(InternalFailure))
+        def failedEvent = buildFinishEvent(999, 'build failed', buildDesc, buildResult)
+
+        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
+        adapter.onEvent(failedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as FinishEvent) >> { DefaultFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "build failed"
+            assert event.descriptor.name == 'some build'
+            assert event.descriptor.parent == null
+            assert event.result instanceof DefaultOperationFailureResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.failures.size() == 1
+        }
+    }
+
+    private InternalOperationDescriptor buildDescriptor(id, String name, InternalOperationDescriptor parent = null) {
+        InternalOperationDescriptor descriptor = Mock(InternalOperationDescriptor)
+        descriptor.getId() >> id
+        descriptor.getName() >> name
+        descriptor.getParentId() >> { parent ? parent.id : null }
+
+        descriptor
+    }
+
+    private InternalOperationDescriptor buildDescriptor(id, String name, String displayName, InternalOperationDescriptor parent = null) {
+        InternalOperationDescriptor descriptor = Mock(InternalOperationDescriptor)
+        descriptor.getId() >> id
+        descriptor.getName() >> name
+        descriptor.getDisplayName() >> displayName
+        descriptor.getParentId() >> { parent ? parent.id : null }
+
+        descriptor
+    }
+
+    private InternalOperationStartedProgressEvent buildStartEvent(long eventTime, String displayName, InternalOperationDescriptor descriptor) {
+        InternalOperationStartedProgressEvent event = Mock(InternalOperationStartedProgressEvent)
+        event.getEventTime() >> eventTime
+        event.getDisplayName() >> displayName
+        event.getDescriptor() >> descriptor
+
+        event
+    }
+
+    private InternalOperationFinishedProgressEvent buildFinishEvent(long eventTime, String displayName, InternalOperationDescriptor descriptor, InternalOperationResult result = null) {
+        InternalOperationFinishedProgressEvent event = Mock(InternalOperationFinishedProgressEvent)
+        event.getEventTime() >> eventTime
+        event.getDisplayName() >> displayName
+        event.getDescriptor() >> descriptor
+        event.getResult() >> result
+
+        event
+    }
+
+    private InternalSuccessResult buildSuccess(long startTime, long endTime) {
+        InternalSuccessResult result = Mock(InternalSuccessResult)
+        result.startTime >> startTime
+        result.endTime >> endTime
+
+        result
+    }
+
+    private InternalFailureResult buildFailure(long startTime, long endTime, InternalFailure failure) {
+        InternalFailureResult result = Mock(InternalFailureResult)
+        result.startTime >> startTime
+        result.endTime >> endTime
+        result.failures >> [failure]
+
+        result
+    }
+
+    private static BuildProgressListenerAdapter createAdapter() {
+        new BuildProgressListenerAdapter([], [], [])
+    }
+
+    private static BuildProgressListenerAdapter createAdapter(ProgressListener buildListener) {
+        new BuildProgressListenerAdapter([], [], [buildListener])
+    }
+
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTaskOperationsTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTaskOperationsTest.groovy
new file mode 100644
index 0000000..257943a
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTaskOperationsTest.groovy
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events.ProgressListener
+import org.gradle.tooling.events.task.*
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener
+import org.gradle.tooling.internal.protocol.InternalFailure
+import org.gradle.tooling.internal.protocol.events.*
+import spock.lang.Specification
+
+class BuildProgressListenerAdapterForTaskOperationsTest extends Specification {
+
+    def "adapter is only subscribing to task progress events if at least one task progress listener is attached"() {
+        when:
+        def adapter = createAdapter()
+
+        then:
+        adapter.subscribedOperations == []
+
+        when:
+        def listener = Mock(ProgressListener)
+        adapter = createAdapter(listener)
+
+        then:
+        adapter.subscribedOperations == [InternalBuildProgressListener.TASK_EXECUTION]
+    }
+
+    def "only TaskProgressEventX instances are processed if a task listener is added"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        adapter.onEvent(Mock(InternalProgressEvent))
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "only TaskProgressEventX instances of known type are processed"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def unknownEvent = Mock(InternalProgressEvent)
+        adapter.onEvent(unknownEvent)
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "conversion of start events throws exception if previous start event with same task descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ':dummy'
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        adapter.onEvent(startEvent)
+        adapter.onEvent(startEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('already available')
+    }
+
+    def "conversion of non-start events throws exception if no previous start event with same task descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ":dummy"
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getParentId() >> null
+
+        def skippedEvent = Mock(InternalOperationFinishedProgressEvent)
+        _ * skippedEvent.getEventTime() >> 999
+        _ * skippedEvent.getDescriptor() >> taskDescriptor
+
+        adapter.onEvent(skippedEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('not available')
+    }
+
+    def "looking up parent operation throws exception if no previous event for parent operation exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def childTaskDescriptor = Mock(InternalTaskDescriptor)
+        _ * childTaskDescriptor.getId() >> 2
+        _ * childTaskDescriptor.getName() >> 'some child'
+        _ * childTaskDescriptor.getParentId() >> 1
+
+        def childEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * childEvent.getDisplayName() >> 'child event'
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childTaskDescriptor
+
+        adapter.onEvent(childEvent)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "conversion of child events expects parent event exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def parentTaskDescriptor = Mock(InternalTaskDescriptor)
+        _ * parentTaskDescriptor.getId() >> 1
+        _ * parentTaskDescriptor.getName() >> 'some parent'
+        _ * parentTaskDescriptor.getParentId() >> null
+
+        def parentEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * parentEvent.getEventTime() >> 999
+        _ * parentEvent.getDescriptor() >> parentTaskDescriptor
+
+        def childTaskDescriptor = Mock(InternalTaskDescriptor)
+        _ * childTaskDescriptor.getId() >> 2
+        _ * childTaskDescriptor.getName() >> 'some child'
+        _ * childTaskDescriptor.getParentId() >> parentTaskDescriptor.getId()
+
+        def childEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childTaskDescriptor
+
+        adapter.onEvent(parentEvent)
+        adapter.onEvent(childEvent)
+
+        then:
+        notThrown(IllegalStateException)
+    }
+
+    def "convert all InternalTaskDescriptor attributes"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ":someTask"
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getDisplayName() >> 'some task in human readable form'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDisplayName() >> 'task started'
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskStartEvent) >> { TaskStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "task started"
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.displayName == 'some task in human readable form'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to TaskStartEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ':dummy'
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDisplayName() >> 'task started'
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskStartEvent) >> { TaskStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "task started"
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to TaskSkippedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ':dummy'
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        def testResult = Mock(InternalTaskSkippedResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+        _ * testResult.getSkipMessage() >> 'SKIPPED'
+
+        def skippedEvent = Mock(InternalOperationFinishedProgressEvent)
+        _ * skippedEvent.getEventTime() >> 999
+        _ * skippedEvent.getDisplayName() >> 'task skipped'
+        _ * skippedEvent.getDescriptor() >> taskDescriptor
+        _ * skippedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // skippedEvent always assumes a previous startEvent
+        adapter.onEvent(skippedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskFinishEvent) >> { TaskFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "task skipped"
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TaskSkippedResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.skipMessage == 'SKIPPED'
+        }
+    }
+
+    def "convert to TaskSucceededEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ':dummy'
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        def testResult = Mock(InternalTaskSuccessResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+        _ * testResult.isUpToDate() >> true
+
+        def succeededEvent = Mock(InternalOperationFinishedProgressEvent)
+        _ * succeededEvent.getEventTime() >> 999
+        _ * succeededEvent.getDisplayName() >> 'task succeeded'
+        _ * succeededEvent.getDescriptor() >> taskDescriptor
+        _ * succeededEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
+        adapter.onEvent(succeededEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskFinishEvent) >> { TaskFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "task succeeded"
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TaskSuccessResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.isUpToDate()
+        }
+    }
+
+    def "convert to TaskFailedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> ':dummy'
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> taskDescriptor
+
+        def testResult = Mock(InternalTaskFailureResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+        _ * testResult.getFailures() >> [Stub(InternalFailure)]
+
+        def failedEvent = Mock(InternalOperationFinishedProgressEvent)
+        _ * failedEvent.getEventTime() >> 999
+        _ * failedEvent.getDisplayName() >> 'task failed'
+        _ * failedEvent.getDescriptor() >> taskDescriptor
+        _ * failedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
+        adapter.onEvent(failedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskFinishEvent) >> { TaskFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "task failed"
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TaskFailureResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.failures.size() == 1
+        }
+    }
+
+    private static BuildProgressListenerAdapter createAdapter() {
+        new BuildProgressListenerAdapter([], [], [])
+    }
+
+    private static BuildProgressListenerAdapter createAdapter(ProgressListener taskListener) {
+        new BuildProgressListenerAdapter([], [taskListener], [])
+    }
+
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTestOperationsTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTestOperationsTest.groovy
new file mode 100644
index 0000000..1e45c9d
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterForTestOperationsTest.groovy
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.events.ProgressListener
+import org.gradle.tooling.events.test.*
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener
+import org.gradle.tooling.internal.protocol.InternalFailure
+import org.gradle.tooling.internal.protocol.events.*
+import spock.lang.Specification
+
+class BuildProgressListenerAdapterForTestOperationsTest extends Specification {
+
+    def "adapter is only subscribing to test progress events if at least one test progress listener is attached"() {
+        when:
+        def adapter = createAdapter()
+
+        then:
+        adapter.subscribedOperations == []
+
+        when:
+        def listener = Mock(ProgressListener)
+        adapter = createAdapter(listener)
+
+        then:
+        adapter.subscribedOperations == [InternalBuildProgressListener.TEST_EXECUTION]
+    }
+
+    def "only TestProgressEventX instances are processed if a test listener is added"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        adapter.onEvent(Stub(InternalProgressEvent))
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "only TestProgressEventX instances of known type are processed"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def unknownEvent = Mock(InternalTestProgressEvent)
+        adapter.onEvent(unknownEvent)
+
+        then:
+        0 * listener.statusChanged(_)
+    }
+
+    def "conversion of start events throws exception if previous start event with same test descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        adapter.onEvent(startEvent)
+        adapter.onEvent(startEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('already available')
+    }
+
+    def "conversion of non-start events throws exception if no previous start event with same test descriptor exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getParentId() >> null
+
+        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * skippedEvent.getEventTime() >> 999
+        _ * skippedEvent.getDescriptor() >> testDescriptor
+
+        adapter.onEvent(skippedEvent)
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('not available')
+    }
+
+    def "looking up parent operation throws exception if no previous event for parent operation exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def childTestDescriptor = Mock(InternalTestDescriptor)
+        _ * childTestDescriptor.getId() >> 2
+        _ * childTestDescriptor.getName() >> 'some child'
+        _ * childTestDescriptor.getParentId() >> 1
+
+        def childEvent = Mock(InternalTestStartedProgressEvent)
+        _ * childEvent.getDisplayName() >> 'child event'
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childTestDescriptor
+
+        adapter.onEvent(childEvent)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "conversion of child events expects parent event exists"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def parentTestDescriptor = Mock(InternalTestDescriptor)
+        _ * parentTestDescriptor.getId() >> 1
+        _ * parentTestDescriptor.getName() >> 'some parent'
+        _ * parentTestDescriptor.getParentId() >> null
+
+        def parentEvent = Mock(InternalTestStartedProgressEvent)
+        _ * parentEvent.getEventTime() >> 999
+        _ * parentEvent.getDescriptor() >> parentTestDescriptor
+
+        def childTestDescriptor = Mock(InternalTestDescriptor)
+        _ * childTestDescriptor.getId() >> 2
+        _ * childTestDescriptor.getName() >> 'some child'
+        _ * childTestDescriptor.getParentId() >> parentTestDescriptor.getId()
+
+        def childEvent = Mock(InternalTestStartedProgressEvent)
+        _ * childEvent.getEventTime() >> 999
+        _ * childEvent.getDescriptor() >> childTestDescriptor
+
+        adapter.onEvent(parentEvent)
+        adapter.onEvent(childEvent)
+
+        then:
+        notThrown(IllegalStateException)
+    }
+
+    def "convert all InternalJvmTestDescriptor attributes"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getDisplayName() >> 'some test suite in human readable form'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
+        _ * testDescriptor.getSuiteName() >> 'some suite'
+        _ * testDescriptor.getClassName() >> 'some class'
+        _ * testDescriptor.getMethodName() >> 'some method'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDisplayName() >> 'test suite started'
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestStartEvent) >> { TestStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test suite started"
+            assert event.descriptor.name == 'some test suite'
+            assert event.descriptor.displayName == 'some test suite in human readable form'
+            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
+            assert event.descriptor.suiteName == 'some suite'
+            assert event.descriptor.className == 'some class'
+            assert event.descriptor.methodName == 'some method'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to TestSuiteStartEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDisplayName() >> 'test suite started'
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestStartEvent) >> { TestStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test suite started"
+            assert event.descriptor.name == 'some test suite'
+            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to TestSuiteSkippedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestSkippedResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+
+        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * skippedEvent.getEventTime() >> 999
+        _ * skippedEvent.getDisplayName() >> 'test suite skipped'
+        _ * skippedEvent.getDescriptor() >> testDescriptor
+        _ * skippedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // skippedEvent always assumes a previous startEvent
+        adapter.onEvent(skippedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test suite skipped"
+            assert event.descriptor.name == 'some test suite'
+            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestSkippedResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+        }
+    }
+
+    def "convert to TestSuiteSucceededEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestSuccessResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+
+        def succeededEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * succeededEvent.getEventTime() >> 999
+        _ * succeededEvent.getDisplayName() >> 'test suite succeeded'
+        _ * succeededEvent.getDescriptor() >> testDescriptor
+        _ * succeededEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
+        adapter.onEvent(succeededEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test suite succeeded"
+            assert event.descriptor.name == 'some test suite'
+            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestSuccessResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+        }
+    }
+
+    def "convert to TestSuiteFailedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test suite'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestFailureResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+        _ * testResult.getFailures() >> [Stub(InternalFailure)]
+
+        def failedEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * failedEvent.getEventTime() >> 999
+        _ * failedEvent.getDisplayName() >> 'test suite failed'
+        _ * failedEvent.getDescriptor() >> testDescriptor
+        _ * failedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
+        adapter.onEvent(failedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test suite failed"
+            assert event.descriptor.name == 'some test suite'
+            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestFailureResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.failures.size() == 1
+        }
+    }
+
+    def "convert to TestStartEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
+        _ * testDescriptor.getClassName() >> 'Foo'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDisplayName() >> 'test started'
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        adapter.onEvent(startEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestStartEvent) >> { TestStartEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test started"
+            assert event.descriptor.name == 'some test'
+            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
+            assert event.descriptor.className == 'Foo'
+            assert event.descriptor.parent == null
+        }
+    }
+
+    def "convert to TestSkippedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
+        _ * testDescriptor.getClassName() >> 'Foo'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestSkippedResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+
+        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * skippedEvent.getEventTime() >> 999
+        _ * skippedEvent.getDisplayName() >> 'test skipped'
+        _ * skippedEvent.getDescriptor() >> testDescriptor
+        _ * skippedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // skippedEvent always assumes a previous startEvent
+        adapter.onEvent(skippedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test skipped"
+            assert event.descriptor.name == 'some test'
+            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
+            assert event.descriptor.className == 'Foo'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestSkippedResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+        }
+    }
+
+    def "convert to TestSucceededEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
+        _ * testDescriptor.getClassName() >> 'Foo'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestSuccessResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+
+        def succeededEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * succeededEvent.getEventTime() >> 999
+        _ * succeededEvent.getDisplayName() >> 'test succeeded'
+        _ * succeededEvent.getDescriptor() >> testDescriptor
+        _ * succeededEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
+        adapter.onEvent(succeededEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test succeeded"
+            assert event.descriptor.name == 'some test'
+            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
+            assert event.descriptor.className == 'Foo'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestSuccessResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+        }
+    }
+
+    def "convert to TestFailedEvent"() {
+        given:
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(listener)
+
+        when:
+        def testDescriptor = Mock(InternalJvmTestDescriptor)
+        _ * testDescriptor.getId() >> 1
+        _ * testDescriptor.getName() >> 'some test'
+        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
+        _ * testDescriptor.getClassName() >> 'Foo'
+        _ * testDescriptor.getParentId() >> null
+
+        def startEvent = Mock(InternalTestStartedProgressEvent)
+        _ * startEvent.getEventTime() >> 999
+        _ * startEvent.getDescriptor() >> testDescriptor
+
+        def testResult = Mock(InternalTestFailureResult)
+        _ * testResult.getStartTime() >> 1
+        _ * testResult.getEndTime() >> 2
+        _ * testResult.getFailures() >> [Stub(InternalFailure)]
+
+        def failedEvent = Mock(InternalTestFinishedProgressEvent)
+        _ * failedEvent.getEventTime() >> 999
+        _ * failedEvent.getDisplayName() >> 'test failed'
+        _ * failedEvent.getDescriptor() >> testDescriptor
+        _ * failedEvent.getResult() >> testResult
+
+        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
+        adapter.onEvent(failedEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TestFinishEvent) >> { TestFinishEvent event ->
+            assert event.eventTime == 999
+            assert event.displayName == "test failed"
+            assert event.descriptor.name == 'some test'
+            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
+            assert event.descriptor.className == 'Foo'
+            assert event.descriptor.parent == null
+            assert event.result instanceof TestFailureResult
+            assert event.result.startTime == 1
+            assert event.result.endTime == 2
+            assert event.result.failures.size() == 1
+        }
+    }
+
+    private static BuildProgressListenerAdapter createAdapter() {
+        new BuildProgressListenerAdapter([], [], [])
+    }
+
+    private static BuildProgressListenerAdapter createAdapter(ProgressListener testListener) {
+        new BuildProgressListenerAdapter([testListener], [], [])
+    }
+
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterTest.groovy
index 8bab478..f2c76fc 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/BuildProgressListenerAdapterTest.groovy
@@ -14,520 +14,95 @@
  * limitations under the License.
  */
 
-
 package org.gradle.tooling.internal.consumer.parameters
 
-import org.gradle.tooling.events.FinishEvent
-import org.gradle.tooling.events.StartEvent
-import org.gradle.tooling.events.test.*
-import org.gradle.tooling.internal.protocol.*
-import org.gradle.tooling.internal.protocol.events.InternalJvmTestDescriptor
-import org.gradle.tooling.internal.protocol.events.InternalTestDescriptor
-import org.gradle.tooling.internal.protocol.events.InternalTestFailureResult
-import org.gradle.tooling.internal.protocol.events.InternalTestFinishedProgressEvent
-import org.gradle.tooling.internal.protocol.events.InternalTestProgressEvent
-import org.gradle.tooling.internal.protocol.events.InternalTestSkippedResult
-import org.gradle.tooling.internal.protocol.events.InternalTestStartedProgressEvent
-import org.gradle.tooling.internal.protocol.events.InternalTestSuccessResult
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.task.TaskStartEvent
+import org.gradle.tooling.internal.protocol.InternalBuildProgressListener
+import org.gradle.tooling.internal.protocol.events.InternalOperationDescriptor
+import org.gradle.tooling.internal.protocol.events.InternalOperationStartedProgressEvent
+import org.gradle.tooling.internal.protocol.events.InternalTaskDescriptor
 import spock.lang.Specification
 
 class BuildProgressListenerAdapterTest extends Specification {
 
-    def "adapter is only subscribing to test progress events if at least one test progress listener is attached"() {
-        when:
-        def adapter = new BuildProgressListenerAdapter([])
-
-        then:
-        adapter.getSubscribedOperations() == []
-
-        when:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        adapter = new BuildProgressListenerAdapter([listener])
-
-        then:
-        adapter.getSubscribedOperations() == [InternalBuildProgressListener.TEST_EXECUTION]
-    }
-
-    def "only TestProgressEventVersionX instances are processed"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        adapter.onEvent(new Object())
-
-        then:
-        0 * listener.statusChanged(_)
-    }
-
-    def "only TestProgressEventVersionX instances of known type are processed"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def unknownEvent = Mock(InternalTestProgressEvent)
-        adapter.onEvent(unknownEvent)
-
-        then:
-        0 * listener.statusChanged(_)
-    }
-
-    def "conversion of start events throws exception if previous start event with same test descriptor exists"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        adapter.onEvent(startEvent)
-        adapter.onEvent(startEvent)
-
-        then:
-        def e = thrown(IllegalStateException)
-        e.message.contains('already available')
-    }
-
-    def "conversion of non-start events throws exception if no previous start event with same test descriptor exists"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getParentId() >> null
-
-        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * skippedEvent.getEventTime() >> 999
-        _ * skippedEvent.getDescriptor() >> testDescriptor
-
-        adapter.onEvent(skippedEvent)
-
-        then:
-        def e = thrown(IllegalStateException)
-        e.message.contains('not available')
-    }
-
-    def "conversion of child events throws exception if no previous parent event exists"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
+    def "adapter can subscribe to multiple progress events"() {
         when:
-        def childTestDescriptor = Mock(InternalTestDescriptor)
-        _ * childTestDescriptor.getId() >> 2
-        _ * childTestDescriptor.getName() >> 'some child'
-        _ * childTestDescriptor.getParentId() >> 1
-
-        def childEvent = Mock(InternalTestStartedProgressEvent)
-        _ * childEvent.getEventTime() >> 999
-        _ * childEvent.getDescriptor() >> childTestDescriptor
-
-        adapter.onEvent(childEvent)
+        def adapter = createAdapter()
 
         then:
-        def e = thrown(IllegalStateException)
-        e.message.contains('not available')
-    }
+        adapter.subscribedOperations == []
 
-    def "conversion of child events expects parent event exists"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def parentTestDescriptor = Mock(InternalTestDescriptor)
-        _ * parentTestDescriptor.getId() >> 1
-        _ * parentTestDescriptor.getName() >> 'some parent'
-        _ * parentTestDescriptor.getParentId() >> null
+        when: 'we register a new test listener'
+        adapter = createAdapter(Mock(ProgressListener), null, null)
 
-        def parentEvent = Mock(InternalTestStartedProgressEvent)
-        _ * parentEvent.getEventTime() >> 999
-        _ * parentEvent.getDescriptor() >> parentTestDescriptor
+        then: 'test execution becomes a subscribed operation'
+        adapter.subscribedOperations as Set == [InternalBuildProgressListener.TEST_EXECUTION] as Set
 
-        def childTestDescriptor = Mock(InternalTestDescriptor)
-        _ * childTestDescriptor.getId() >> 2
-        _ * childTestDescriptor.getName() >> 'some child'
-        _ * childTestDescriptor.getParentId() >> parentTestDescriptor.getId()
+        when: 'we register a new task listener'
+        adapter = createAdapter(Mock(ProgressListener), Mock(ProgressListener), null)
 
-        def childEvent = Mock(InternalTestStartedProgressEvent)
-        _ * childEvent.getEventTime() >> 999
-        _ * childEvent.getDescriptor() >> childTestDescriptor
+        then: 'task execution becomes a subscribed operation'
+        adapter.subscribedOperations as Set == [InternalBuildProgressListener.TEST_EXECUTION, InternalBuildProgressListener.TASK_EXECUTION] as Set
 
-        adapter.onEvent(parentEvent)
-        adapter.onEvent(childEvent)
+        when: 'we register a new build listener'
+        adapter = createAdapter(Mock(ProgressListener), Mock(ProgressListener), Mock(ProgressListener))
 
-        then:
-        notThrown(IllegalStateException)
+        then: 'build execution becomes a subscribed operation'
+        adapter.subscribedOperations as Set == [InternalBuildProgressListener.TEST_EXECUTION, InternalBuildProgressListener.TASK_EXECUTION, InternalBuildProgressListener.BUILD_EXECUTION] as Set
     }
 
-    def "convert all JvmTestDescriptorVersion1 attributes"() {
+    def "parent descriptor of a descriptor can be of a different type"() {
         given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getDisplayName() >> 'some test suite in human readable form'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
-        _ * testDescriptor.getSuiteName() >> 'some suite'
-        _ * testDescriptor.getClassName() >> 'some class'
-        _ * testDescriptor.getMethodName() >> 'some method'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDisplayName() >> 'test suite started'
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        adapter.onEvent(startEvent)
-
-        then:
-        1 * listener.statusChanged(_ as StartEvent) >> { StartEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test suite started"
-            assert event.descriptor.name == 'some test suite'
-            assert event.descriptor.displayName == 'some test suite in human readable form'
-            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
-            assert event.descriptor.suiteName == 'some suite'
-            assert event.descriptor.className == 'some class'
-            assert event.descriptor.methodName == 'some method'
-            assert event.descriptor.parent == null
+        def listener = Mock(ProgressListener)
+        def adapter = createAdapter(null, listener, null)
+
+        when:
+        def buildDescriptor = Mock(InternalOperationDescriptor)
+        _ * buildDescriptor.getId() >> 1
+        _ * buildDescriptor.getName() >> 'my build'
+        _ * buildDescriptor.getParentId() >> null
+
+        def taskDescriptor = Mock(InternalTaskDescriptor)
+        _ * taskDescriptor.getId() >> 2
+        _ * taskDescriptor.getName() >> 'some task'
+        _ * taskDescriptor.getTaskPath() >> ':some:path'
+        _ * taskDescriptor.getParentId() >> buildDescriptor.getId()
+
+        def buildStartEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * buildStartEvent.getEventTime() >> 999
+        _ * buildStartEvent.getDisplayName() >> 'build started'
+        _ * buildStartEvent.getDescriptor() >> buildDescriptor
+
+        def taskStartEvent = Mock(InternalOperationStartedProgressEvent)
+        _ * taskStartEvent.getEventTime() >> 1001
+        _ * taskStartEvent.getDisplayName() >> 'task started'
+        _ * taskStartEvent.getDescriptor() >> taskDescriptor
+
+        adapter.onEvent(buildStartEvent)
+        adapter.onEvent(taskStartEvent)
+
+        then:
+        1 * listener.statusChanged(_ as TaskStartEvent) >> { TaskStartEvent event ->
+            assert event.eventTime == 1001
+            assert event.displayName == 'task started'
+            assert event.descriptor.name == 'some task'
+            assert event.descriptor.taskPath == ':some:path'
+            assert event.descriptor.parent.name == 'my build'
+            assert event.descriptor.parent.parent == null
         }
     }
 
-    def "convert to TestSuiteStartedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDisplayName() >> 'test suite started'
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        adapter.onEvent(startEvent)
-
-        then:
-        1 * listener.statusChanged(_ as StartEvent) >> { StartEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test suite started"
-            assert event.descriptor.name == 'some test suite'
-            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
-            assert event.descriptor.parent == null
-        }
+    BuildProgressListenerAdapter createAdapter() {
+        createAdapter(null, null, null)
     }
 
-    def "convert to TestSuiteSkippedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestSkippedResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-
-        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * skippedEvent.getEventTime() >> 999
-        _ * skippedEvent.getDisplayName() >> 'test suite skipped'
-        _ * skippedEvent.getDescriptor() >> testDescriptor
-        _ * skippedEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // skippedEvent always assumes a previous startEvent
-        adapter.onEvent(skippedEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test suite skipped"
-            assert event.descriptor.name == 'some test suite'
-            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestSkippedResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-        }
-    }
-
-    def "convert to TestSuiteSucceededEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestSuccessResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-
-        def succeededEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * succeededEvent.getEventTime() >> 999
-        _ * succeededEvent.getDisplayName() >> 'test suite succeeded'
-        _ * succeededEvent.getDescriptor() >> testDescriptor
-        _ * succeededEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
-        adapter.onEvent(succeededEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test suite succeeded"
-            assert event.descriptor.name == 'some test suite'
-            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestSuccessResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-        }
-    }
-
-    def "convert to TestSuiteFailedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test suite'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_SUITE
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestFailureResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-        _ * testResult.getFailures() >> [Stub(InternalFailure)]
-
-        def failedEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * failedEvent.getEventTime() >> 999
-        _ * failedEvent.getDisplayName() >> 'test suite failed'
-        _ * failedEvent.getDescriptor() >> testDescriptor
-        _ * failedEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
-        adapter.onEvent(failedEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test suite failed"
-            assert event.descriptor.name == 'some test suite'
-            assert event.descriptor.jvmTestKind == JvmTestKind.SUITE
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestFailureResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-            assert event.result.failures.size() == 1
-        }
+    BuildProgressListenerAdapter createAdapter(ProgressListener testListener) {
+        createAdapter(testListener, null, null)
     }
 
-    def "convert to TestStartedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
-        _ * testDescriptor.getClassName() >> 'Foo'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDisplayName() >> 'test started'
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        adapter.onEvent(startEvent)
-
-        then:
-        1 * listener.statusChanged(_ as StartEvent) >> { StartEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test started"
-            assert event.descriptor.name == 'some test'
-            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
-            assert event.descriptor.className == 'Foo'
-            assert event.descriptor.parent == null
-        }
-    }
-
-    def "convert to TestSkippedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
-        _ * testDescriptor.getClassName() >> 'Foo'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestSkippedResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-
-        def skippedEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * skippedEvent.getEventTime() >> 999
-        _ * skippedEvent.getDisplayName() >> 'test skipped'
-        _ * skippedEvent.getDescriptor() >> testDescriptor
-        _ * skippedEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // skippedEvent always assumes a previous startEvent
-        adapter.onEvent(skippedEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test skipped"
-            assert event.descriptor.name == 'some test'
-            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
-            assert event.descriptor.className == 'Foo'
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestSkippedResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-        }
-    }
-
-    def "convert to TestSucceededEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
-        _ * testDescriptor.getClassName() >> 'Foo'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestSuccessResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-
-        def succeededEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * succeededEvent.getEventTime() >> 999
-        _ * succeededEvent.getDisplayName() >> 'test succeeded'
-        _ * succeededEvent.getDescriptor() >> testDescriptor
-        _ * succeededEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // succeededEvent always assumes a previous startEvent
-        adapter.onEvent(succeededEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test succeeded"
-            assert event.descriptor.name == 'some test'
-            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
-            assert event.descriptor.className == 'Foo'
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestSuccessResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-        }
-    }
-
-    def "convert to TestFailedEvent"() {
-        given:
-        final TestProgressListener listener = Mock(TestProgressListener)
-        def adapter = new BuildProgressListenerAdapter([listener])
-
-        when:
-        def testDescriptor = Mock(InternalJvmTestDescriptor)
-        _ * testDescriptor.getId() >> 1
-        _ * testDescriptor.getName() >> 'some test'
-        _ * testDescriptor.getTestKind() >> InternalJvmTestDescriptor.KIND_ATOMIC
-        _ * testDescriptor.getClassName() >> 'Foo'
-        _ * testDescriptor.getParentId() >> null
-
-        def startEvent = Mock(InternalTestStartedProgressEvent)
-        _ * startEvent.getEventTime() >> 999
-        _ * startEvent.getDescriptor() >> testDescriptor
-
-        def testResult = Mock(InternalTestFailureResult)
-        _ * testResult.getStartTime() >> 1
-        _ * testResult.getEndTime() >> 2
-        _ * testResult.getFailures() >> [Stub(InternalFailure)]
-
-        def failedEvent = Mock(InternalTestFinishedProgressEvent)
-        _ * failedEvent.getEventTime() >> 999
-        _ * failedEvent.getDisplayName() >> 'test failed'
-        _ * failedEvent.getDescriptor() >> testDescriptor
-        _ * failedEvent.getResult() >> testResult
-
-        adapter.onEvent(startEvent) // failedEvent always assumes a previous startEvent
-        adapter.onEvent(failedEvent)
-
-        then:
-        1 * listener.statusChanged(_ as FinishEvent) >> { FinishEvent event ->
-            assert event.eventTime == 999
-            assert event.displayName == "test failed"
-            assert event.descriptor.name == 'some test'
-            assert event.descriptor.jvmTestKind == JvmTestKind.ATOMIC
-            assert event.descriptor.className == 'Foo'
-            assert event.descriptor.parent == null
-            assert event.result instanceof TestFailureResult
-            assert event.result.startTime == 1
-            assert event.result.endTime == 2
-            assert event.result.failures.size() == 1
-        }
+    BuildProgressListenerAdapter createAdapter(ProgressListener testListener, ProgressListener taskListener, ProgressListener buildListener) {
+        new BuildProgressListenerAdapter(testListener ? [testListener] : [], taskListener ? [taskListener] : [], buildListener ? [buildListener] : [])
     }
 
 }
diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/TestOutputStream.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/TestOutputStream.groovy
index 9273b8e..8000cc5 100644
--- a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/TestOutputStream.groovy
+++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/TestOutputStream.groovy
@@ -32,4 +32,10 @@ class TestOutputStream extends OutputStream {
             return buffer.toString()
         }
     }
+
+    void reset() {
+        synchronized (buffer) {
+            buffer.reset()
+        }
+    }
 }
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index 0103fb9..b76e6fe 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -12,6 +12,7 @@ dependencies {
     testCompile libraries.groovy
 
     // lots of integTest errors otherwise
+    integTestCompile project(':internalIntegTesting')
     integTestRuntime project(':ide')
     integTestRuntime project(':buildInit')
     integTestRuntime project(':buildComparison')
@@ -21,7 +22,6 @@ dependencies {
 }
 
 useTestFixtures()
-useTestFixtures(project: ':launcher', sourceSet: "integTest")
 
 integTestTasks.all {
     dependsOn({ rootProject.getTasksByName('publishLocalArchives', true) }, ':distributions:binZip')
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy
index e194fc9..7b22c18 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy
@@ -27,6 +27,7 @@ import org.gradle.openapi.external.ui.*
 import org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.testfixtures.internal.NativeServicesTestFixture
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
@@ -48,6 +49,7 @@ import static org.hamcrest.Matchers.*
  * gradually be replaced by other GUI tests.
  */
 @Requires(TestPrecondition.SWING)
+ at LeaksFileHandles
 class OpenApiUiTest {
 
     @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
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 124a137..8ea7d14 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.wrapper
 
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.file.LeaksFileHandles
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -23,12 +24,13 @@ class SystemPropertiesHandlerTest extends Specification {
     @Rule
     TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
+    @LeaksFileHandles
     def parsesPropertiesFile() {
         File propFile = tmpDir.file('props')
         Properties props = new Properties()
         props.putAll a: 'b', 'systemProp.c': 'd', 'systemProp.': 'e'
         props.store(new FileOutputStream(propFile), "")
-        
+
         expect:
         [c: 'd'] == SystemPropertiesHandler.getSystemProperties(propFile)
     }
diff --git a/version.txt b/version.txt
index 6b4950e..95e3ba8 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-2.4
+2.5

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



More information about the pkg-java-commits mailing list